Lufer

  • 首页
  • 编程
  • 学习笔记
  • 日常折腾
Lufer
Code the World
  1. 首页
  2. 学习笔记
  3. WriteUP
  4. 正文

网络安全技能实训 WP

2025年7月1日 34点热度 0人点赞 0条评论

MISC

Shadow

题目

这里是影子的领域,这里藏着光与影的秘密。 那是转瞬即逝的光,还是永恒的影?

题解

用nc连环境,得到一些提示

用python连一下,结果复制出来,替换掉特殊字符,可以看见flag

Weird Letter

题目

初入 CTF 的你,收到了一封奇怪的信:全文似乎看不懂,但是又仿佛能看出来其中的含义。 所以,这封信件到底隐藏了什么样的秘密呢?

题解

Ejhx VZYtn,

    Q wrhu shjx tklytva nxqvi xov blre ggs pppw qet'rf juphebcc bwh wnbiujtkgz hu YBU fzqklfsnkl. Gl nkc zqgm, brzuauzxtedg xv s squdnhr vufekvtql ee mbsf IML vwwtahfwds, bsk om'y bblwgwsds tp mhbx g zdkl jqvuqsufujbtz db bwh vyeffwltm kgrngewaem tfhotbwnto bwdl qqe vxlj.

    Bt vawahlu sqyqyvmkgiwu, bwh Nyfeojyk ttw Rwmhdj shpijyy txx ekxjosh bhpnjkl. Zat Rqvhfuqe dnwnxx bh w xdoqqkpifikmov hqjhwajttjtu ibvatn bwdl kret f ykiktievv nwovosi au xtvxlptu hbzioyldm sxhoivhk. Ys's b uvcxxyjh mcfjootjtu zhue, qqb xw uqm bf hygvqxs qaxqy vqerzltve tcwtnvai. Shf Hhklgk rexwhj yr a tntvekk hqjhwajttjtu ibvatn bwdl igigyz ktia aabihj ym tij wrtogiafi eq q eiyjk tnsutn wu sgihtjtuy wupc ppt ddfgacja. Shjxgj kgbhjngsfwnr uyuazh pghd aeahtvkw tjkgbhjhoo yliatbfqmh vmsg at yok Tjopjktg Wdbrzuaoht Liwvsdjt (ZET). FLY by t huubhlhhc fsjxrvmxkv poyeqiumt zagm jomh d cux tp juikeii wvs gwsqyqy kgmg. Bi'o ltvawmee yv xxybhp iiwssjs gwvs xbxc ppt pgis aeahtvkw rkuexluqs bsk ol cbsatn xkuc tp juikeii omcvajhvf ihzt. Glniutwjyb eohyeizbdj qh dfeshfw psiukiwvi hfsqyqypug zxrdvxtmu shby'z alkw xj KII uxzlmjumxy. Thuubhlhhc fsjxrvmxkv jvwi swp plel, g ijxtxf cux aoi h vkooppm zhq, jn eohyeiz tcz ltfjoot efag. Bz'l rkubrfbx utjk og jbvebpo kyfnbybxxy tcz atfmhd cprtagovppqdq hhntphvrl.

    Og rkvromihoo, hyeizhvniekq yr a gfzibttievv dft bhbqskgmbcc ajebubt umhz'l ikxpqrdd jn CUK jntretjotv. Oxdtijy eha'kt jml wg SSF pw ht xditnqtquuc CUKlx, ntwtnaidfthnh yok woyuaztql umcsdwzbug iakwqagtet nz klyxcpqpo le rudhlkw og idmhh uxzlmjumxy. Dtax tahbnrjsn ggj aprm uxf sqadpptz zadom rlhxdrt!

    Dvak lepc qh idqf oqju hkgvt zlt5fs3t-d877-4150-a3bh-441i6lw8250g vakat ejqbe.

Cjzz kkzpnlh,
Gj. 0f

试了下单表替换,无果

维吉尼亚加密,先测试可能的秘钥长度,再爆破秘钥。

import re


# 密文
f = open('D:\\est.txt', 'r')
raw = f.read().upper()
print(raw)
ctext = re.sub(r'[^a-zA-Z]', '', raw)



# ctext = 'KCCPKBGUFDPHQTYAVINRRTMVGRKDNBVFDETDGILTXRGUDDKOTFMBPVGEGLTGCKQRACQCWDNAWCRXIZAKFTLEWRPTYCQKYVXCHKFTPONCQQRHJVAJUWETMCMSPKQDYHJVDAHCTRLSVSKCGCZQQDZXGSFRLSWCWSJTBHAFSIASPRJAHKJRJUMVGKMITZHFPDISPZLVLGWTFPLKKEBDPGCEBSHCTJRWXBAFSPEZQNRWXCVYCGAONWDDKACKAWBBIKFTIOVKCGGHJVLNHIFFSQESVYCLACNVRWBBIREPBBVFEXOSCDYGZWPFDTKFQIYCWHJVLNHIQIBTKHJVNPIST'



# 破解密钥长度 计算重合指数

ctext.lower()
clen = len(ctext)
print("密文长度为:", clen)
maxlenkey = int(input("输入测试密钥长度上限:"))
lenkey = 0


def groupzmexist(lenkey, list):  # 统计'第list'组中各字母出现次数,以数组形式返回
    zmexist = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
    for i in range(list - 1, clen, lenkey):
        for j in range(0, 26):
            if (ctext[i] == chr(97 + j) or ctext[i] == chr(65 + j)):
                zmexist[j] = zmexist[j] + 1
    return zmexist

def count_ic(lenkey):  # 密钥长lenkey,返回该密钥长度时平均IC
    average = 0.0
    for m in range(1, lenkey + 1):
        ic = 0.0
        for i in range(0, 26):
            ic = ic + (groupzmexist(lenkey, m)[i] * (groupzmexist(lenkey, m)[i] - 1)) / (
                        sum(groupzmexist(lenkey, m)) * (sum(groupzmexist(lenkey, m)) - 1))
        average = ic + average
    return (average / lenkey)


max = 0  # IC取>0.06中最大的数
for i in range(1, maxlenkey + 1):  # 计算密钥长
    num = count_ic(i)
    if (num >= 0.06 and num > max):  # 取>0.06中最大的数
        max = num
        print("最可能密钥长度为:", i)
        lenkey = i

得到秘钥的可能长度是16

import vigenerecipher
import string
import re

ciphertext = "O&NPTF{Y0u_yepognizeq_the_Couphu's_psog.}"
key = "H&N2025"


# 带字符的维吉尼亚,字符保留
def vigenere_decrypt1(ciphertext: str, key: str) -> str:
    # 只对这些字符做解密,其他一律保留
    alphabet = string.ascii_uppercase + string.ascii_lowercase + string.digits
    # 提取密钥中也在 alphabet 里的字符,并转换成移位值
    shifts = [alphabet.index(k) for k in key if k in alphabet]
    if not shifts:
        raise ValueError("密钥中不包含可用解密字符!")

    plain = []
    j = 0
    for c in ciphertext:
        if c in alphabet:
            # 只对字母和数字做反向移位
            s = shifts[j % len(shifts)]
            idx = alphabet.index(c)
            # 解密:从密文字母索引里减去移位,再 mod 回去
            pi = (idx - s) % len(alphabet)
            plain.append(alphabet[pi])
            j += 1
        else:
            # 花括号、下划线、&、单引号等等都原样保留
            plain.append(c)
    return ''.join(plain)


# 密钥带数字的维吉尼亚,只有 A–Z/a–z 产生位移,其他字符(数字、符号)都当作 0 位移。
upper = string.ascii_uppercase
lower = string.ascii_lowercase


def vigenere_decrypt2(txt: str, key: str) -> str:
    plain = []
    j = 0                       # 只在遇到字母时推进密钥
    for c in txt:
        if not c.isalpha():     # 非字母:原样抄过去
            plain.append(c)
            continue

        k = key[j % len(key)]
        # ★ 关 键 处 ★ ── 数字 → 0 位移
        if k.isalpha():
            shift = ord(k.upper()) - ord('A')
        else:                   # 数字 / 符号
            shift = 0

        if c.isupper():
            pi = (ord(c) - ord('A') - shift) % 26
            plain.append(chr(pi + ord('A')))
        else:
            pi = (ord(c) - ord('a') - shift) % 26
            plain.append(chr(pi + ord('a')))

        j += 1

    return ''.join(plain)


# 文本猜词,攻击维吉尼亚

def gcd(a, b):
    if a < b:
        a, b = b, a
    if a % b == 0:
        return b
    else:
        return gcd(b, a % b)


def findstr(Ctext, str_):
    interval_list = []
    loc = 0
    array_locs = []
    while loc < len(Ctext):
        loc = Ctext.find(str_, loc)

        if loc == -1:
            break
        array_locs.append(loc)
        loc = loc + 1
    index = 0
    while index + 1 < len(array_locs):
        interval = array_locs[index + 1] - array_locs[index]
        print("间隔:", interval)
        interval_list.append(interval)
        index = index + 1
    interval_len = gcd(interval_list[0], interval_list[1])
    print("当重复值为", str_, "时,两两之间距离的最大公因数:", interval_len, "\n")
    return interval_len


def find_repeat(Ciphertext, repeat_list):
    while len(Ciphertext) > 200:
        Ciphertext = list(Ciphertext)
        Ciphertext.pop(1)
        Ciphertext = ''.join(Ciphertext)
        list1 = re.findall(r'.{3}', Ciphertext)

        list2 = [0] * len(list1)

        for i in range(len(list1)):
            for j in range(len(list1)):
                if (list1[i] == list1[j]):
                    list2[i] = list2[i] + 1

        max_len = max(list2)
        a = list2.index(max(list2))

        if list1[a] not in repeat_list:
            if max_len == 3:
                repeat_list.append(list1[a])
                print("重复次数次数:", max_len)
                print("重复值:", list1[a])
                print("\n")


def check_len(Ciphertext, interval_len):
    ListCiphertext = list(Ciphertext)
    Keylength = 1

    while Keylength < interval_len + 1:
        # 指数初始化为0
        CoincidenceIndex = 0

        # 使用切片分组
        for i in range(Keylength):
            Numerator = 0
            PresentCipherList = ListCiphertext[i::Keylength]

            # 使用集合去重,计算每一子密文组重合指数
            for Letter in set(PresentCipherList):
                Numerator += PresentCipherList.count(Letter) * (PresentCipherList.count(Letter) - 1)
            CoincidenceIndex += Numerator / (len(PresentCipherList) * (len(PresentCipherList) - 1))

        # 求各子密文组的拟重合指数的平均值
        Average = CoincidenceIndex / Keylength
        Keylength += 1

        # 均值>0.6即可退出循环
        if Average > 0.06:
            break

    Keylength -= 1
    print("经重合指数验证后,密钥长度最可能为:", Keylength, "\n")
    return Keylength


def keyword(Ciphertext, keylength):
    ListCiphertext = list(Ciphertext)
    # 标准数据来源于课本
    Standard = {'A': 0.082, 'B': 0.015, 'C': 0.028, 'D': 0.043, 'E': 0.127, 'F': 0.022, 'G': 0.020, 'H': 0.061,
                'I': 0.070, 'J': 0.002, 'K': 0.008, 'L': 0.040, 'M': 0.024, 'N': 0.067, 'O': 0.075, 'P': 0.019,
                'Q': 0.001, 'R': 0.060, 'S': 0.063, 'T': 0.091, 'U': 0.028, 'V': 0.010, 'W': 0.023, 'X': 0.001,
                'Y': 0.020, 'Z': 0.001}

    while True:
        KeyResult = []

        for i in range(keylength):
            # 使用切片分组
            PresentCipherList = ListCiphertext[i::keylength]

            # 初始化重合指数最大值为0,检验移动位数对应字符以*代替
            QuCoincidenceMax = 0
            KeyLetter = "*"

            # 遍历移动的位数
            # m是密钥对应的英文字母
            for m in range(26):
                # 初始化当前移动位数的重合互指数为0
                QuCoincidencePresent = 0

                # 遍历计算重合指数:各个字符的频率*对应英文字符出现的标准频率---的和
                for Letter in set(PresentCipherList):
                    # fi/n
                    LetterFrequency = PresentCipherList.count(Letter) / len(PresentCipherList)

                    # 标准频率
                    # ord(Letter) - 65是将letter对应的字母化为26内的数值,然后与m运算,得到的k是对应的明文字母
                    k = chr((ord(Letter) - 65 - m) % 26 + 65)
                    StandardFrequency = Standard[k]

                    # 计算重合互指数,累加遍历26个英文字母
                    QuCoincidencePresent = QuCoincidencePresent + LetterFrequency * StandardFrequency

                # 保存遍历过程中重合指数的最大值,同时保存对应应对的位数,即对应key的字符
                if QuCoincidencePresent > QuCoincidenceMax:
                    QuCoincidenceMax = QuCoincidencePresent
                    # m是26个英文对应的位置,从0开始,+65是因为A在ascii中是65
                    KeyLetter = chr(m + 65)
            print("第", i + 1, "个密钥字母为:", KeyLetter, "对应的重合互指数为:", QuCoincidenceMax)
            # 保存当前位置key的值,退出循环,进行下一组子密文移动位数的尝试
            KeyResult.append(KeyLetter)
        # 列表转为字符串
        Key = "".join(KeyResult)
        break
    return Key

def hand_attack_key(m,c):
    s = vigenerecipher.decode(c,m)
    print(s)

    return s



if __name__ == '__main__':
    # home = input('home?')
    # cipher = input('cipher?')
    # raw = open(home + cipher,'r').read().upper()
    f = open('D:\\est.txt', 'r')
    raw = f.read().upper()
    print(raw)
    Ciphertext = re.sub(r'[^a-zA-Z]', '', raw)
    print(Ciphertext)
    repeat_list = []
    find_repeat(Ciphertext, repeat_list)
    print("kasisiki测试法:\n重复列表", repeat_list)
    # for m in repeat_list:
    #     interval_len = findstr(Ciphertext, m)

    key_len = 16

    KeyResult = keyword(Ciphertext, key_len)

    print("密钥最可能为:", KeyResult, "\n")

得到秘钥后维吉尼亚解密获得flag。

神秘数据

题目

小明收到一份神秘的文件,观察了文章的段落很久之后终于露出了笑容,你能找到里面隐藏的数据吗? flag格式,flag{xxx},其中xxx为解码得到的值

题解

题目是一段文章,观察可以发现有的后面空一行,有的后面空两行,记录为二进制01,然后二进制转字符获得flag。

Cypto

猜猜我是谁

LQGSK6GUJWNAK4GOJMIIG=J2NWURVU5MJWEMJUZ25TA=WC2SYDMLKCVLYSGLWKMS6=X2GEUHX2GOT2ENUXVNTYQ=

题目

四段同样类型的文本,先栅栏,得到一串base64,解码获得flag。

Web

Nodejs Bypass

题目

没有人比我更懂 nodejs

题解

附件中有两个js文件

const fs = require("fs");
const SECRET_COOKIE = process.env.SECRET_COOKIE || "this_is_testing_cookie"

const flag1 = fs.readFileSync("/flag1")
const flag2 = fs.readFileSync("/flag2")

function merge(target, source) {
    for (let key in source) {
        if (key == "__proto__") {
            continue; // no proto, please bypass
        }
        if (key in target && key in source) {
            merge(target[key], source[key])
        } else {
            target[key] = source[key]
        }
    }
}

function LoginController(req, res) {
    try {
        let user = {}
        merge(user, req.body)

        if (user.username !== "admin" || user.password !== Math.random().toString()) {
            res.status(401).type("text/html").send("Login Failed")
        } else {
            res.cookie("user", SECRET_COOKIE)
            req.user = "admin"
            res.redirect("/flag1")
        }
    } catch (e) {
        console.log(e)
        res.status(401).type("text/html").send("What the heck")
    }
}

function Flag1Controller(req, res) {
    try {
        if (req.cookies.user === SECRET_COOKIE || req.user == "admin") {
            res.setHeader("This_Is_The_Flag1", flag1.toString().trim())
            res.status(200).type("text/html").send("Login success. Welcome,admin!")
        } else {
            res.status(401).type("text/html").send("Unauthorized")
        }
    } catch (__) { }
}

function Flag2Controller(req, res) {
    let checkcode = req.body.checkcode ? req.body.checkcode : 1234;
    console.log(req.body)
    if (checkcode.length === 16) {
        try {
            checkcode = checkcode.toLowerCase()
            if (checkcode !== "aGr5AtSp55dRacer") {
                res.status(403).json({ "msg": "Invalid Checkcode1:" + checkcode })
            }
        } catch (__) { }
        res.status(200).type("text/html").json({ "msg": "You Got Another Part Of Flag: " + flag2.toString().trim() })
    } else {
        res.status(403).type("text/html").json({ "msg": "Invalid Checkcode2:" + checkcode })
    }
}

module.exports = {
    LoginController,
    Flag1Controller,
    Flag2Controller
}
const express = require("express")
const fs = require("fs")
const cookieParser = require("cookie-parser");
const controller = require("./controller")

const app = express();
const PORT = Number(process.env.PORT) || 80
const HOST = '0.0.0.0'


app.use(express.urlencoded({ extended: false }))
app.use(cookieParser())
app.use(express.json())

app.use(express.static('static'))

app.get("/", (res) => {
    res.sendFile(__dirname, "static/index.html")
})

app.post("/", (req, res) => {
    controller.LoginController(req, res)
})

app.get("/flag1", (req, res) => {
    controller.Flag1Controller(req, res)
})

app.post("/flag2", (req, res) => {
    controller.Flag2Controller(req, res)
})

app.listen(PORT, HOST, () => {
    console.log(`Server is listening on Host ${HOST} Port ${PORT}.`)
})

审计代码,发现存在flag1和flag2两个入口

先看flag1,需要检测req.user==admin。

通过constructor进行原型链污染,把用户设置为admin。

然后访问flag1,发现已经成功登录。

重放这个请求,可以得到前半截flag。

然后审计flag2,发现需要传入checkcode,长度为16,并且要与指定字符串相等,因为先执行了toLowerCase函数,因此不能直接传入,需要产生异常跳出try代码块才能执行后续部分。

传入数组,进行比较时会抛出异常,因为长度要求是16,因此构造长度为16的数组。

用GET会提示错误,那就用POST。

One Number SQL

题目

GZTime 把 flag 藏到数据库里,只给大家看 flag 的一小截,但是这不影响你拿到 flag 吧 ~

题解

访问页面,得到一句提示

小 GZ 露出 flag 的一角,但是这离成功远远不够,你能将 flag 整条拖出来吗 

审计代码,发现存在隐藏提示。

<p class="secret">something secrets in /hint <span class="emoji key"></span></p>

访问hint,得到代码。

def waf(str):
    length = 0
    for c in str:
        if(c.isdigit()):
            length += length * 10 + int(c)
    return length > 0 and length < 10

@app.route('/getFlag')
def getFlag():
    flag_len = request.args.get("len")

    try:
        if(waf(flag_len)):
            result = cursor.execute(f"select substr(flag,1,{flag_len}) from flag")
            result = result.fetchone()
            print(len(result[0]))
            if(len(result[0])):
                return render_template('getflag.html',message=result[0]) # Only the first query information of sql can be obtained
            else:
                return render_template('getflag.html',message="nothing here")
        else:
            return render_template('getflag.html',message="len should be 0 < len < 10")
    except:
        return render_template('getflag.html',message="Error! Please Try Again Meow~ ")

审计代码发现,需要访问getFlag,传入参数len,用于执行select语句,但是len最大不能超过10,因此构造语句,断开并重新构造查询语句。

传入一个小于10的数字绕过WAF,然后同步构造from flag where 'a'='b',通过假条件让返回的结果为空,再通过union select执行我们的真实操作,select flag from flag,添加两个减号,注释掉后续内容。

len=1) from flag where 'a'='b' union select flag from flag--

The Moment of Token

题目

转瞬即逝的时机,转瞬即逝的宝藏。

题解

审计代码,发现有一句提示。

 <!-- maybe... int(time.time()) - int(password) < 5 -->

那么输入的密码要和时间戳的差要小于5,抓包改时间戳,header里面发现一个jwt的token。

在线JWT解密,发现有个gift字段,是base32加密后的flag。

标签: 暂无
最后更新:2025年7月4日

Lufer

新的一天开始啦

点赞
< 上一篇
下一篇 >

文章评论

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回复

文章目录
  • MISC
    • Shadow
      • 题目
      • 题解
    • Weird Letter
      • 题目
      • 题解
    • 神秘数据
      • 题目
      • 题解
  • Cypto
    • 猜猜我是谁
      • 题目
  • Web
    • Nodejs Bypass
      • 题目
      • 题解
    • One Number SQL
      • 题目
      • 题解
    • The Moment of Token
      • 题目
      • 题解

COPYRIGHT © 2025 lufer.cc.

Theme Kratos Made By Seaton Jiang

鲁ICP备2021045819号