NullCon CTF 2022 - Sourcer
Website :
Listing files in /usr/src/app/files/:
/app.py will give the Source Code of this Challenge.
Flag is not here, it is in ../
We Need to Perform a Path Traversal to Get One Directory Back to Read the Flag.
Source Code :
#!/usr/bin/python
import http.server
import threading
import socketserver
import re
import os
from io import StringIO
BASEPATH="/usr/src/app"
class FileServerHandler(http.server.SimpleHTTPRequestHandler):
server_version = "Fil3serv3r"
def do_GET(self):
self.send_response(-1337)
self.send_header('Content-Length', -1337)
s = StringIO()
path = self.path.lstrip("/")
counter = 0
while ".." in path:
path = path.replace("../", "")
counter += 1
if counter > 10:
s.write(f"No")
self.end_headers()
self.wfile.write(s.getvalue().encode())
return
fpath = os.path.join(BASEPATH, "files", path)
s.write(f"Welcome to @gehaxelt's file server.\n\n")
if len(fpath) <= len(BASEPATH):
self.send_header('Content-Type', 'text/plain')
s.write(f"Hm, this path is not within {BASEPATH}")
elif os.path.exists(fpath) and os.path.isfile(fpath):
self.send_header('Content-Type', 'application/octet-stream')
with open(fpath, 'r') as f:
s.write(f.read())
elif os.path.exists(fpath) and os.path.isdir(fpath):
self.send_header('Content-Type', 'text/plain')
s.write(f"Listing files in {fpath}:\n")
for f in os.listdir(fpath):
s.write(f"- {f}\n")
else:
self.send_header('Content-Type', 'text/plain')
s.write(f"Oops, not found.")
self.end_headers()
self.wfile.write(s.getvalue().encode())
if __name__ == "__main__":
PORT = 8000
HANDLER = FileServerHandler
with socketserver.ThreadingTCPServer(("0.0.0.0", PORT), HANDLER) as httpd:
print("serving at port", PORT)
httpd.serve_forever()
GET Route :
As Mentioned Below, ‘/
’ will Return file in /usr/src/app/files/
Flag is in /usr/src/app/flag.txt
Base Path :
BASEPATH="/usr/src/app"
FileServerHandler :
class FileServerHandler(http.server.SimpleHTTPRequestHandler):
server_version = "Fil3serv3r"
def do_GET(self):
self.send_response(-1337)
self.send_header('Content-Length', -1337)
s = StringIO()
path = self.path.lstrip("/")
counter = 0
while ".." in path:
path = path.replace("../", "")
counter += 1
if counter > 10:
s.write(f"No")
self.end_headers()
self.wfile.write(s.getvalue().encode())
return
fpath = os.path.join(BASEPATH, "files", path)
s.write(f"Welcome to @gehaxelt's file server.\n\n")
if len(fpath) <= len(BASEPATH):
self.send_header('Content-Type', 'text/plain')
s.write(f"Hm, this path is not within {BASEPATH}")
elif os.path.exists(fpath) and os.path.isfile(fpath):
self.send_header('Content-Type', 'application/octet-stream')
with open(fpath, 'r') as f:
s.write(f.read())
elif os.path.exists(fpath) and os.path.isdir(fpath):
self.send_header('Content-Type', 'text/plain')
s.write(f"Listing files in {fpath}:\n")
for f in os.listdir(fpath):
s.write(f"- {f}\n")
else:
self.send_header('Content-Type', 'text/plain')
s.write(f"Oops, not found.")
self.end_headers()
self.wfile.write(s.getvalue().encode())
path.lstrip :
path = self.path.lstrip("/")
lstrip will Just Remove the /
comes Left side of the String
While Loop :
while ".." in path:
path = path.replace("../", "")
counter += 1
if counter > 10:
s.write(f"No")
self.end_headers()
self.wfile.write(s.getvalue().encode())
return
The Above While Loop will Run If the Path Variable Have 2 Dots (..
)
path.replace :
path = path.replace("../", "")
path.replace("../","")
will Search for ../
in Path Variable and Replace that with ""
.
path.join
fpath = os.path.join(BASEPATH, "files", path)
We Have Control of the Path Variable!
Exploit Idea :
After Some Trial and Error, I came Across this Interesting thing…
When the Path Variable Starts with /
, The Overall Path is Overwritten. Now We have Control of the Path. Let’s Exploit
If Else Condition Statements :
s.write(f"Welcome to @gehaxelt's file server.\n\n")
if len(fpath) <= len(BASEPATH):
self.send_header('Content-Type', 'text/plain')
s.write(f"Hm, this path is not within {BASEPATH}")
elif os.path.exists(fpath) and os.path.isfile(fpath):
self.send_header('Content-Type', 'application/octet-stream')
with open(fpath, 'r') as f:
s.write(f.read())
elif os.path.exists(fpath) and os.path.isdir(fpath):
self.send_header('Content-Type', 'text/plain')
s.write(f"Listing files in {fpath}:\n")
for f in os.listdir(fpath):
s.write(f"- {f}\n")
else:
self.send_header('Content-Type', 'text/plain')
s.write(f"Oops, not found.")
self.end_headers()
self.wfile.write(s.getvalue().encode())
These If Else Statements are Responsible for Sending Response.
- If the Length of
fpath
is equal or lesser that BASEPATH
, the Server will return, Hm, this path is not within {BASEPATH}
Exploit :
Note: We Can Control Path Variable.
Our Input is Filtered Before Passed into
fpath = os.path.join(BASEPATH, "files", path)
To Exploit, Our Payload Want to Looks Like this, fpath = os.path.join(BASEPATH, "files", "/usr/src/app/flag.txt")
to Read the Flag.
Final Payload : ..//usr/src/app/flag.txt
Let me Explain Why this Works:
In While Loop, Replace Function is Called,
path = path.replace(“../”, "")
After This Replace Function,
- Then, Our Path Variable is Passed to
.join
And that Works 🙂
FLAG: ENO{PYTH0Ns_0sp4thj01n_fun_l0l}