Skip to content

Commit ef57386

Browse files
committed
make custom text vuln harder
1 parent b86279c commit ef57386

File tree

6 files changed

+35
-53
lines changed

6 files changed

+35
-53
lines changed

checker/src/checker.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,7 @@ async def check_replay_for_flag(con: Connection, uuid: str) -> Optional[str]:
323323
"""
324324

325325

326-
@checker.putflag(1)
326+
@checker.putflag(0)
327327
async def putflag_custom(
328328
task: PutflagCheckerTaskMessage,
329329
db: ChainDB,
@@ -360,7 +360,7 @@ async def putflag_custom(
360360
con.log_error('Failed to save custom text', e)
361361

362362

363-
@checker.getflag(1)
363+
@checker.getflag(0)
364364
async def getflag_custom(
365365
task: GetflagCheckerTaskMessage,
366366
db: ChainDB,
@@ -372,7 +372,7 @@ async def getflag_custom(
372372
try:
373373
db_data = await db.get('userdata')
374374
except KeyError:
375-
logger.error("Missing replayId in database entry from putflag")
375+
logger.error("Missing data in database entry from putflag")
376376
raise MumbleException('Getflag 1 failed')
377377

378378
con = Connection(logger, client)
@@ -398,7 +398,7 @@ async def getflag_custom(
398398
assert_equals(task.flag, flag, "Flag was found to be incorrect")
399399

400400

401-
@checker.exploit(1)
401+
@checker.exploit(0)
402402
async def exploit_custom(
403403
task: ExploitCheckerTaskMessage,
404404
searcher: FlagSearcher,
@@ -418,7 +418,7 @@ async def exploit_custom(
418418
con.log_debug(f'Got attack info: {task.attack_info}')
419419
files = {
420420
'_token': (None, csrf_token),
421-
'textName': (None, f'/data/uploads/{task.attack_info}/flag'),
421+
'textName': (None, f'//data/uploads/{task.attack_info}/flag'),
422422
'textFile': ('x.txt', 'x', 'text/plain'),
423423
'MAX_FILE_SIZE': (None, '3145728')
424424
}
@@ -433,7 +433,7 @@ async def exploit_custom(
433433
con.log_error('Failed to save custom text', e)
434434

435435
try:
436-
response = await client.get(f'/text?file=/data/uploads/{task.attack_info}/flag')
436+
response = await client.get(f'/text?file=//data/uploads/{task.attack_info}/flag')
437437
response.raise_for_status()
438438
data = response.json()
439439
return data['text']

service/src/Foundation.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ data RaceState = RaceState
5252
raceWordCount :: Int,
5353
raceStartTime :: Maybe UTCTime,
5454
raceProgress :: Map UserId Int, -- character index
55-
raceWPM :: Map UserId Double, -- current WPM - ADD THIS
55+
raceWPM :: Map UserId Double, -- current WPM
5656
raceCompleted :: Map UserId (Double, Double), -- final (wpm, accuracy)
5757
raceTextLength :: Int
5858
}

service/src/Handler/CustomText.hs

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
{-# LANGUAGE BlockArguments #-}
2+
{-# LANGUAGE FlexibleContexts #-}
23
{-# LANGUAGE LambdaCase #-}
34
{-# LANGUAGE MultiParamTypeClasses #-}
45
{-# LANGUAGE OverloadedStrings #-}
@@ -20,7 +21,7 @@ import System.Directory
2021
( createDirectoryIfMissing,
2122
doesFileExist,
2223
)
23-
import System.FilePath.Posix (joinPath, normalise, splitPath, takeExtension, takeFileName)
24+
import System.FilePath.Posix (takeExtension, takeFileName)
2425

2526
customTextForm :: Html -> MForm Handler (FormResult (Text, FileInfo), Widget)
2627
customTextForm extra = do
@@ -81,13 +82,6 @@ getCustomTextR = do
8182
setTitle "TimeType | CustomText"
8283
$(widgetFile "custom-text")
8384

84-
sanitizePath :: FilePath -> FilePath
85-
sanitizePath =
86-
joinPath
87-
. filter (\p -> p /= "./" && p /= "../")
88-
. splitPath
89-
. normalise
90-
9185
postCustomTextR :: Handler Html
9286
postCustomTextR = do
9387
((result, widget), enctype) <- runFormPost customTextForm
@@ -110,7 +104,7 @@ postCustomTextR = do
110104
let ext =
111105
map toLower $
112106
takeExtension (T.unpack $ fileName fileInfo)
113-
sanitizedName = sanitizePath (T.unpack filename)
107+
sanitizedName = takeBaseName (T.unpack filename)
114108

115109
if ext /= ".txt"
116110
then respond status400 "Only .txt files are allowed"

service/src/Model.hs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
{-# LANGUAGE DeriveGeneric #-}
44
{-# LANGUAGE DerivingStrategies #-}
55
{-# LANGUAGE EmptyDataDecls #-}
6+
{-# LANGUAGE FlexibleContexts #-}
67
{-# LANGUAGE FlexibleInstances #-}
78
{-# LANGUAGE GADTs #-}
89
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
@@ -17,8 +18,9 @@
1718

1819
module Model where
1920

20-
import ClassyPrelude.Yesod
21+
import ClassyPrelude.Yesod hiding (joinPath)
2122
import Database.Persist.Quasi
23+
import System.FilePath.Posix (joinPath, normalise, splitPath)
2224
import Yesod.Auth.HashDB (HashDBUser (..))
2325

2426
data LobbyStatus = LobbyStatusWaiting | LobbyStatusInProgress | LobbyStatusFinished
@@ -30,6 +32,28 @@ instance ToJSON LobbyStatus
3032

3133
instance FromJSON LobbyStatus
3234

35+
class PathSanitizer a where
36+
takeBaseName :: a -> FilePath
37+
38+
instance PathSanitizer FilePath where
39+
takeBaseName = sanitizePath
40+
41+
sanitizePath :: FilePath -> FilePath
42+
sanitizePath p =
43+
let (s, suffix) = case span (== '/') p of
44+
(sl@('/' : '/' : _), rest) -> (sl, rest)
45+
_ -> ("", dropWhile (== '/') p)
46+
parts =
47+
filter
48+
( \x ->
49+
x `notElem` ["./", "../"]
50+
&& not (".." `isInfixOf` x)
51+
)
52+
. splitPath
53+
. normalise
54+
$ suffix
55+
in s ++ joinPath parts
56+
3357
share
3458
[mkPersist sqlSettings, mkMigrate "migrateAll"]
3559
$(persistFileWith lowerCaseSettings "config/models.persistentmodels")

service/src/Settings.hs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -105,12 +105,6 @@ compileTimeAppSettings =
105105
Error e -> error e
106106
Success settings -> settings
107107

108-
-- The following two functions can be used to combine multiple CSS or JS files
109-
-- at compile time to decrease the number of http requests.
110-
-- Sample usage (inside a Widget):
111-
--
112-
-- > $(combineStylesheets 'StaticR [style1_css, style2_css])
113-
114108
combineStylesheets :: Name -> [Route Static] -> Q Exp
115109
combineStylesheets = combineStylesheets'
116110
(appSkipCombining compileTimeAppSettings)

service/src/Settings/StaticFiles.hs

Lines changed: 0 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -7,34 +7,4 @@ module Settings.StaticFiles where
77
import Settings (appStaticDir, compileTimeAppSettings)
88
import Yesod.Static (staticFiles)
99

10-
-- This generates easy references to files in the static directory at compile time,
11-
-- giving you compile-time verification that referenced files exist.
12-
-- Warning: any files added to your static directory during run-time can't be
13-
-- accessed this way. You'll have to use their FilePath or URL to access them.
14-
--
15-
-- For example, to refer to @static/js/script.js@ via an identifier, you'd use:
16-
--
17-
-- js_script_js
18-
--
19-
-- If the identifier is not available, you may use:
20-
--
21-
-- StaticFile ["js", "script.js"] []
2210
staticFiles (appStaticDir compileTimeAppSettings)
23-
24-
-- If you prefer to updating the references by force
25-
-- -- especially when you are devloping like `stack exec -- yesod devel` --
26-
-- you can update references by chaning file stamp.
27-
--
28-
-- On linux or Unix-like system, you can use
29-
-- shell> touch /Path/To/Settings/StaticFiles.hs
30-
--
31-
-- or save without changes on your favorite editor.
32-
-- so yesod devel will re-compile automatically and generate the refereces
33-
-- including new one.
34-
--
35-
-- In this way you can use on your shakespearean template(s)
36-
-- Let's say you have image on "yourStaticDir/img/background-1.jpg"
37-
-- you can use the reference of the file on shakespearean templates like bellow
38-
--
39-
-- /* note: `-' becomes '_' as well */
40-
-- @{StaticR img_background_1_jpg}

0 commit comments

Comments
 (0)