From f3a15b00e9a5fe9ec37f276d7306d8f991b05cbc Mon Sep 17 00:00:00 2001 From: nemunaire Date: Mon, 25 Jan 2016 03:09:22 +0100 Subject: [PATCH] Too much things --- admin/api.go | 1 + admin/api_events.go | 17 ++ admin/api_exercice.go | 2 +- admin/api_stats.go | 43 +++++ backend/submission.go | 38 +++- frontend/static/css/fic.css | 44 +++++ frontend/static/index.html | 1 - frontend/static/js/angular-animate.min.js | 55 ++++++ frontend/static/js/app.js | 15 +- frontend/static/js/public.js | 136 +++++++++++++++ frontend/static/public.html | 201 ++++++++++++++++++++++ frontend/static/views/rank.html | 2 +- libfic/db.go | 10 ++ libfic/event.go | 64 +++++++ libfic/exercice.go | 28 ++- 15 files changed, 640 insertions(+), 17 deletions(-) create mode 100644 admin/api_events.go create mode 100644 admin/api_stats.go create mode 100644 frontend/static/js/angular-animate.min.js create mode 100644 frontend/static/js/public.js create mode 100644 frontend/static/public.html create mode 100644 libfic/event.go diff --git a/admin/api.go b/admin/api.go index f2cb30ea..cd7174aa 100644 --- a/admin/api.go +++ b/admin/api.go @@ -15,6 +15,7 @@ type DispatchFunction func([]string, []byte) (interface{}, error) var apiRoutes = map[string]*(map[string]DispatchFunction){ "version": &ApiVersionRouting, "ca": &ApiCARouting, + "events": &ApiEventsRouting, "themes": &ApiThemesRouting, "teams": &ApiTeamsRouting, } diff --git a/admin/api_events.go b/admin/api_events.go new file mode 100644 index 00000000..66958376 --- /dev/null +++ b/admin/api_events.go @@ -0,0 +1,17 @@ +package main + +import ( + "srs.epita.fr/fic-server/libfic" +) + +var ApiEventsRouting = map[string]DispatchFunction{ + "GET": getEvents, +} + +func getEvents(args []string, body []byte) (interface{}, error) { + if evts, err := fic.GetEvents(); err != nil { + return nil, err + } else { + return evts, nil + } +} diff --git a/admin/api_exercice.go b/admin/api_exercice.go index dcb9e7b7..4edc4bf1 100644 --- a/admin/api_exercice.go +++ b/admin/api_exercice.go @@ -12,7 +12,7 @@ type uploadedExercice struct { Title string Statement string Hint string - Depend *int + Depend *int64 Gain int VideoURI string } diff --git a/admin/api_stats.go b/admin/api_stats.go new file mode 100644 index 00000000..294bab36 --- /dev/null +++ b/admin/api_stats.go @@ -0,0 +1,43 @@ +package main + +import ( + "fmt" + + "srs.epita.fr/fic-server/libfic" +) + +type statsTheme struct { + SolvedByLevel []int `json:"solvedByLevel"` +} + +type stats struct { + Themes map[string]statsTheme `json:"themes"` + TryRank []int64 `json:"tryRank"` +} + +func genStats() (interface{}, error) { + ret := map[string]statsTheme{} + + if themes, err := fic.GetThemes(); err != nil { + return nil, err + } else { + for _, theme := range themes { + if exercices, err := theme.GetExercices(); err != nil { + return nil, err + } else { + exos := map[string]exportedExercice{} + for _, exercice := range exercices { + exos[fmt.Sprintf("%d", exercice.Id)] = exportedExercice{ + exercice.Title, + exercice.Gain, + exercice.SolvedCount(), + exercice.TriedTeamCount(), + } + } + ret[fmt.Sprintf("%d", theme.Id)] = statsTheme{} + } + } + + return ret, nil + } +} diff --git a/backend/submission.go b/backend/submission.go index ce266ef0..166cefea 100644 --- a/backend/submission.go +++ b/backend/submission.go @@ -40,18 +40,21 @@ func treatSubmission(pathname string, team_id string, exercice_id string) { log.Println(id, "[WRN] Unable to change team name:", err) } genTeamMyFile(team) + if _, err := fic.NewEvent(fmt.Sprintf("Souhaitons bonne chance à l'équipe %s qui vient de nous rejoindre !", team.Name), "alert-info"); err != nil { + log.Println(id, "[WRN] Unable to create event:", err) + } } if err := os.Remove(pathname); err != nil { log.Println(id, "[ERR]", err) } } else if eid, err := strconv.Atoi(exercice_id); err != nil { log.Println(id, "[ERR]", err) - } else if exercice, err := fic.GetExercice(eid); err != nil { + } else if exercice, err := fic.GetExercice(int64(eid)); err != nil { log.Println(id, "[ERR]", err) } else if theme, err := exercice.GetTheme(); err != nil { log.Println(id, "[ERR]", err) } else { - if solved, err := exercice.CheckResponse(keys, team); err != nil { + if solved, firstTry, err := exercice.CheckResponse(keys, team); err != nil { log.Println(id, "[ERR]", err) } else if solved { exercice.Solved(team) @@ -59,11 +62,27 @@ func treatSubmission(pathname string, team_id string, exercice_id string) { if err := os.Remove(pathname); err != nil { log.Println(id, "[ERR]", err) } + + // Write event + if lvl, err := exercice.GetLevel(); err != nil { + log.Println(id, "[ERR]", err) + } else if _, err := fic.NewEvent(fmt.Sprintf("L'équipe %s a résolu le %de challenge %s !", team.Name, lvl, theme.Name), "alert-success"); err != nil { + log.Println(id, "[WRN] Unable to create event:", err) + } } else { log.Printf("%s Team %d submit an invalid solution for exercice %d (%s : %s)\n", id, team.Id, exercice.Id, theme.Name, exercice.Title) if err := os.Remove(pathname); err != nil { log.Println(id, "[ERR]", err) } + + // Write event + if firstTry { + if lvl, err := exercice.GetLevel(); err != nil { + log.Println(id, "[ERR]", err) + } else if _, err := fic.NewEvent(fmt.Sprintf("L'équipe %s tente le %de challenge %s !", team.Name, lvl, theme.Name), "alert-warning"); err != nil { + log.Println(id, "[WRN] Unable to create event:", err) + } + } } genTeamMyFile(team) } @@ -72,6 +91,11 @@ func treatSubmission(pathname string, team_id string, exercice_id string) { func genTeamMyFile(team fic.Team) error { dirPath := path.Join(TeamsDir, fmt.Sprintf("%d", team.Id)) + started := true + if _, err := os.Stat(path.Join(TeamsDir, "started")); os.IsNotExist(err) { + started = false + } + if s, err := os.Stat(dirPath); os.IsNotExist(err) { os.MkdirAll(dirPath, 0777) } else if !s.IsDir() { @@ -86,5 +110,15 @@ func genTeamMyFile(team fic.Team) error { return err } + if !started { + if my, err := fic.MyJSONTeam(&team, false); err != nil { + return err + } else if j, err := json.Marshal(my); err != nil { + return err + } else if err := ioutil.WriteFile(path.Join(dirPath, "wait.json"), j, 0666); err != nil { + return err + } + } + return nil } diff --git a/frontend/static/css/fic.css b/frontend/static/css/fic.css index ba80cef7..4ca6a5de 100644 --- a/frontend/static/css/fic.css +++ b/frontend/static/css/fic.css @@ -2,6 +2,10 @@ body { overflow-y: scroll; } +.text-bold { + font-weight: bolder; +} + .navbar { margin-bottom: 0; } @@ -56,7 +60,11 @@ keyframes clockanim { h1 small.authors { float: right; + font-style: italic; font-size: 42%; +} +.lead small.authors { + color: #7a8288; font-style: italic; } @@ -69,3 +77,39 @@ h1 small.authors { -webkit-filter: invert(100%); filter: invert(100%); } + +.heading { + font-style: italic; + margin-top: -7px; + text-align: right; +} + +.repeated-item.ng-enter, +.repeated-item.ng-leave { + transition-duration: 1s; +} + +.repeated-item.ng-enter { + opacity: 0; + transform: translateY(-1000px); +} + +.repeated-item.ng-enter.ng-enter-active { + opacity: 1; + transform: translateY(0px); +} + +.repeated-item.ng-leave.ng-leave-active { + opacity: 0; +} +.col-sm-8 .repeated-item.ng-leave.ng-leave-active { + transform: translateX(-800px); +} +.col-sm-4 .repeated-item.ng-leave.ng-leave-active { + transform: translateX(800px); +} + +.repeated-item.ng-enter-stagger { + transition-delay: 0.7s; + transition-duration: 0s; +} diff --git a/frontend/static/index.html b/frontend/static/index.html index 03ff7099..5afdc602 100644 --- a/frontend/static/index.html +++ b/frontend/static/index.html @@ -96,7 +96,6 @@ - diff --git a/frontend/static/js/angular-animate.min.js b/frontend/static/js/angular-animate.min.js new file mode 100644 index 00000000..fdbd3c1b --- /dev/null +++ b/frontend/static/js/angular-animate.min.js @@ -0,0 +1,55 @@ +/* + AngularJS v1.4.9 + (c) 2010-2015 Google, Inc. http://angularjs.org + License: MIT +*/ +(function(E,l,Va){'use strict';function xa(a,b,c){if(!a)throw Ka("areq",b||"?",c||"required");return a}function ya(a,b){if(!a&&!b)return"";if(!a)return b;if(!b)return a;aa(a)&&(a=a.join(" "));aa(b)&&(b=b.join(" "));return a+" "+b}function La(a){var b={};a&&(a.to||a.from)&&(b.to=a.to,b.from=a.from);return b}function W(a,b,c){var d="";a=aa(a)?a:a&&R(a)&&a.length?a.split(/\s+/):[];u(a,function(a,m){a&&0=a&&(a=k,k=0,b.push(f),f=[]);f.push(t.fn);t.children.forEach(function(a){k++;c.push(a)});a--}f.length&&b.push(f);return b}(c)}var h=[],l=T(a);return function(x,z,v){function k(a){a=a.hasAttribute("ng-animate-ref")?[a]:a.querySelectorAll("[ng-animate-ref]");var b=[];u(a,function(a){var c=a.getAttribute("ng-animate-ref");c&&c.length&&b.push(a)});return b}function N(a){var b=[],c={};u(a,function(a,g){var d=F(a.element),H=0<=["enter","move"].indexOf(a.event), +d=a.structural?k(d):[];if(d.length){var f=H?"to":"from";u(d,function(a){var b=a.getAttribute("ng-animate-ref");c[b]=c[b]||{};c[b][f]={animationID:g,element:I(a)}})}else b.push(a)});var g={},d={};u(c,function(c,f){var k=c.from,w=c.to;if(k&&w){var B=a[k.animationID],t=a[w.animationID],A=k.animationID.toString();if(!d[A]){var h=d[A]={structural:!0,beforeStart:function(){B.beforeStart();t.beforeStart()},close:function(){B.close();t.close()},classes:y(B.classes,t.classes),from:B,to:t,anchors:[]};h.classes.length? +b.push(h):(b.push(B),b.push(t))}d[A].anchors.push({out:k.element,"in":w.element})}else k=k?k.animationID:w.animationID,w=k.toString(),g[w]||(g[w]=!0,b.push(a[k]))});return b}function y(a,b){a=a.split(" ");b=b.split(" ");for(var c=[],d=0;d=O&&b>=K&&(ha=!0,r())}function H(){function b(){if(!l){A(!1);u(y,function(a){g.style[a[0]]=a[1]});k(a,e);f.addClass(a,ba);if(p.recalculateTimingStyles){ka= +g.className+" "+ca;fa=Ia(g,ka);C=v(g,ka,fa);Z=C.maxDelay;n=Math.max(Z,0);K=C.maxDuration;if(0===K){r();return}p.hasTransitions=0s.expectedEndTime)?J.cancel(s.timer):h.push(r)}H&&(t=J(c,t,!1),h[0]={timer:t,expectedEndTime:d},h.push(r),a.data("$$animateCss",h));if(da.length)a.on(da.join(" "),B);e.to&&(e.cleanupStyles&&Fa(G,g,Object.keys(e.to)),Aa(a,e))}}function c(){var b=a.data("$$animateCss");if(b){for(var d=1;d= 10) { + return input; + } else { + return "0" + input; + } + } + }) + .controller("TimeController", function($scope, $rootScope, $http, $timeout) { + $scope.time = {}; + var initTime = function() { + $timeout.cancel($scope.cbi); + $scope.cbi = $timeout(initTime, 10000); + $http.get("/time.json").success(function(time) { + console.log("upd time"); + time.he = (new Date()).getTime(); + sessionStorage.userService = angular.toJson(time); + }); + }; + initTime(); + + function updTime() { + $timeout.cancel($scope.cb); + $scope.cb = $timeout(updTime, 1000); + if (sessionStorage.userService) { + var time = angular.fromJson(sessionStorage.userService); + var srv_cur = (Date.now() + (time.cu * 1000 - time.he)) / 1000; + var remain = time.du; + if (time.st == Math.floor(srv_cur)) { + $scope.refresh(true); + $rootScope.startIn = 0; + } + if (time.st > 0 && time.st <= srv_cur) { + remain = time.st + time.du - srv_cur; + } else if (time.st > 0) { + $rootScope.startIn = Math.floor(time.st - srv_cur); + } + if (remain < 0) { + remain = 0; + $scope.time.end = true; + $scope.time.expired = true; + } else if (remain < 60) { + $scope.time.end = false; + $scope.time.expired = true; + } else { + $scope.time.end = false; + $scope.time.expired = false; + } + $scope.time.duration = time.du; + $scope.time.remaining = remain; + $scope.time.hours = Math.floor(remain / 3600); + $scope.time.minutes = Math.floor((remain % 3600) / 60); + $scope.time.seconds = Math.floor(remain % 60); + } + } + updTime(); + }) + .controller("DataController", function($scope, $http, $rootScope, $timeout) { + $rootScope.refresh = function() { + $timeout.cancel($scope.cbd); + $scope.cbd = $timeout($rootScope.refresh, 4200); + $http.get("/demo.json").success(function(demo) { + $scope.my = demo; + }); + $http.get("/events.json").success(function(evts) { + var events = {}; + var now = new Date(); + angular.forEach(evts, function(event, key) { + events["e" + event.id] = event; + event.since = now.getTime() - now.getTimezoneOffset() * 60000 - Date.parse(event.time); + }); + $scope.events = events; + }); + $http.get("/public.json").success(function(scene) { + $scope.scene = scene; + }); + $http.get("/stats.json").success(function(stats) { + $scope.stats = stats; + }); + $http.get("/themes.json").success(function(themes) { + $scope.themes = themes; + $scope.max_gain = 0; + angular.forEach(themes, function(theme, key) { + this[key].exercice_count = Object.keys(theme.exercices).length; + this[key].gain = 0; + angular.forEach(theme.exercices, function(ex, k) { + this.gain += ex.gain; + }, theme); + $scope.max_gain += theme.gain; + }, themes); + }); + $http.get("/teams.json").success(function(teams) { + $scope.teams_count = Object.keys(teams).length + $scope.teams = teams; + + $scope.rank = []; + angular.forEach($scope.teams, function(team, tid) { + team.id = tid; + if (team.rank) { + this.push(team); + } + }, $scope.rank); + }); + console.log("refresh!"); + } + $rootScope.refresh(); + }); diff --git a/frontend/static/public.html b/frontend/static/public.html new file mode 100644 index 00000000..46dfa240 --- /dev/null +++ b/frontend/static/public.html @@ -0,0 +1,201 @@ + + + + + Challenge FIC2016 + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +
+ +
+

Bienvenue au challenge forensic !

+

+ Avant de vous installer, venez récupérer votre clef USB auprès + de notre équipe. Elle contient le certificat qui vous permettra + de vous authentifier auprès de notre serveur. +

+

+ Une fois connecté au réseau, vous pouvez accéder au serveur à : + + https://fic.srs.epita.fr/ + +

+
+ +
+
+

Le challenge forensic 2016 est sur le point de démarrer !

+
+
{{ startIn }}
+
+ +
+

Bienvenue au challenge forensic !

+

+ Durant ce challenge, les équipes doivent remonter des scénarii + d'attaques auxquels nos systèmes d'information font faces + chaque jour : fuite de données, compromission d'un poste de + travail, exploitation de vulnérabilités d'un site web, ... +

+

+ Pour valider un challenge, chaque participant va télécharger, + en fonction des exercices : soit des journaux d'évènements, des + extraits de trafic réseau ou même des copies figées de la + mémoire vive de machines malveillantes, pour essayer de + comprendre comment l'attaquant a contourné la sécurité de la + machine et quelles actions hostiles ont été effectuées. +

+
+ +
+

{{ s.title }}

+

{{ s.lead }}

+

+

{{ s.text }}

+
+ +
+
+

{{ s.title }}

+
+
{{ s.text }}
+
+
+ +
+

+ {{ themes[my.exercices[s.id].theme_id].exercices[s.id].title }} du challenge {{ themes[my.exercices[s.id].theme_id].name }} + par {{ themes[my.exercices[s.id].theme_id].authors }} +

+

+
    +
  • {{ themes[my.exercices[s.id].theme_id].exercices[s.id].gain }} points
  • +
  • {{ my.exercices[s.id].files.length }} fichiers disponibles
  • +
  • Tenté par {{ themes[my.exercices[s.id].theme_id].exercices[s.id].tried }} équipes
  • +
  • {{ my.exercices[s.id].solved_number }} tentatives
  • +
  • Résolu par {{ themes[my.exercices[s.id].theme_id].exercices[s.id].solved }} équipes
  • +
+
+ + + + + + + + + + + + + + + + + + +
Niveau 1Niveau 2Niveau 3Niveau 4Niveau 5
{{ themes[th].name }}{{ ex.solved }}{{ ex.tried }}
+ + + + + + + + + + + + + + + + +
PlaceÉquipeScore
{{ r.rank }}ere{{ r.name }}{{ r.score }}
+ +
+ +
+ +
+ +
+
+
{{ e.since | since }}
+ +
+
+ + + Epita + +
+
+ +
+ + + + + + + + + diff --git a/frontend/static/views/rank.html b/frontend/static/views/rank.html index 2f1c23e6..2c496ad7 100644 --- a/frontend/static/views/rank.html +++ b/frontend/static/views/rank.html @@ -9,7 +9,7 @@ - + {{ team[field] }} diff --git a/libfic/db.go b/libfic/db.go index 7658a686..a1bd1c87 100644 --- a/libfic/db.go +++ b/libfic/db.go @@ -17,6 +17,16 @@ func DBInit(dsn string) error { func DBCreate() error { if _, err := db.Exec(` +CREATE TABLE IF NOT EXISTS events( + id_event INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT, + txt VARCHAR(255) NOT NULL UNIQUE, + kind ENUM('alert-default', 'alert-primary', 'alert-info', 'alert-warning', 'alert-danger', 'alert-success') NOT NULL, + time TIMESTAMP NOT NULL +); +`); err != nil { + return err + } + if _, err := db.Exec(` CREATE TABLE IF NOT EXISTS themes( id_theme INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT, name VARCHAR(255) NOT NULL UNIQUE, diff --git a/libfic/event.go b/libfic/event.go new file mode 100644 index 00000000..7a91c0a1 --- /dev/null +++ b/libfic/event.go @@ -0,0 +1,64 @@ +package fic + +import ( + "time" +) + +type Event struct { + Id int64 `json:"id"` + Kind string `json:"kind"` + Text string `json:"txt"` + Time time.Time `json:"time"` +} + +func GetEvents() ([]Event, error) { + if rows, err := DBQuery("SELECT id_event, txt, kind, time FROM events ORDER BY time DESC LIMIT 6"); err != nil { + return nil, err + } else { + defer rows.Close() + + var events = make([]Event, 0) + for rows.Next() { + var e Event + if err := rows.Scan(&e.Id, &e.Text, &e.Kind, &e.Time); err != nil { + return nil, err + } + events = append(events, e) + } + if err := rows.Err(); err != nil { + return nil, err + } + + return events, nil + } +} + +func NewEvent(txt string, kind string) (Event, error) { + if res, err := DBExec("INSERT INTO events (txt, kind, time) VALUES (?, ?, ?)", txt, kind, time.Now()); err != nil { + return Event{}, err + } else if eid, err := res.LastInsertId(); err != nil { + return Event{}, err + } else { + return Event{eid, txt, kind, time.Now()}, nil + } +} + +func (e Event) Update() (int64, error) { + if res, err := DBExec("UPDATE events SET txt = ?, kind = ?, time = ? WHERE id_event = ?", e.Text, e.Kind, e.Time, e.Id); err != nil { + return 0, err + } else if nb, err := res.RowsAffected(); err != nil { + return 0, err + } else { + return nb, err + } +} + +func (e Event) Delete() (int64, error) { + if res, err := DBExec("DELETE FROM events WHERE id_event = ?", e.Id); err != nil { + return 0, err + } else if nb, err := res.RowsAffected(); err != nil { + return 0, err + } else { + return nb, err + } +} diff --git a/libfic/exercice.go b/libfic/exercice.go index 8162286f..e06f2bfb 100644 --- a/libfic/exercice.go +++ b/libfic/exercice.go @@ -133,6 +133,20 @@ func (e Exercice) Delete() (int64, error) { } } +func (e Exercice) GetLevel() (int, error) { + dep := e.Depend + nb := 1 + for dep != nil { + nb += 1 + if edep, err := GetExercice(*dep); err != nil { + return nb, err + } else { + dep = edep.Depend + } + } + return nb, nil +} + func (e Exercice) NewTry(t Team) error { if _, err := DBExec("INSERT INTO exercice_tries (id_exercice, id_team, time) VALUES (?, ?, ?)", e.Id, t.Id, time.Now()); err != nil { return err @@ -176,19 +190,19 @@ func (e Exercice) TriedCount() int64 { } } -func (e Exercice) CheckResponse(resps map[string]string, t Team) (bool, error) { - s, _, _ := t.HasSolved(e) +func (e Exercice) CheckResponse(resps map[string]string, t Team) (bool, bool, error) { + s, tm, _ := t.HasSolved(e) if s { - return true, nil + return true, false, nil } if err := e.NewTry(t); err != nil { - return false, err + return false, false, err } else if keys, err := e.GetKeys(); err != nil { - return false, err + return false, false, err } else { if len(keys) < 1 { - return true, errors.New("Exercice with no key registered") + return true, false, errors.New("Exercice with no key registered") } valid := true @@ -203,6 +217,6 @@ func (e Exercice) CheckResponse(resps map[string]string, t Team) (bool, error) { } } - return valid, nil + return valid, tm.Unix() == 0, nil } }