2025-帕鲁杯(parloo杯)-crypto

两道和web结合的题目,学习了一下

RSA_Quartic_Quandary

题目:

from Crypto.Util.number import getPrime, bytes_to_long
import math

FLAG = b'**************'


def generate_parameters(bit_length=512):
    p = getPrime(bit_length)
    q = getPrime(bit_length)
    n = p * q
    e = 65537
    phi = (p - 1) * (q - 1)
    d = pow(e, -1, phi)
    s = p ** 4 + q ** 4
    return n, e, d, s, p, q


def main():
    n, e, d, s, p, q = generate_parameters()
    c = pow(bytes_to_long(FLAG), e, n)

    with open('output.txt', 'w') as f:
        f.write(f"n = {n}\n")
        f.write(f"e = {e}\n")
        f.write(f"c = {c}\n")
        f.write(f"s = {s}\n")

    print("[+] Parameters saved to output.txt")


if __name__ == "__main__":
    main()

就是解个方程

s=p4+q4s = p^4+q^4

n=pqn = p * q

exp:

n = 125997816345753096048865891139073286898143461169514858050232837657906289840897974068391106608902082960171083817785532702158298589600947834699494234633846206712414663927142998976208173208829799860130354978308649020815886262453865196867390105038666506017720712272359417586671917060323891124382072599746305448903
e = 65537
c = 16076213508704830809521504161524867240789661063230251272973700316524961511842110066547743812160813341691286895800830395413052502516451815705610447484880112548934311914559776633140762863945819054432492392315491109745915225117227073045171062365772401296382778452901831550773993089344837645958797206220200272941
s = 35935569267272146368441512592153486419244649035623643902985220815940198358146024590300394059909370115858091217597774010493938674472746828352595432824315405933241792789402041405932624651226442192749572918686958461029988244396875361295785103356745756304497466567342796329331150560777052588294638069488836419744297241409127729615544668547101580333420563318486256358906310909703237944327684178950282413703357020770127158209107658407007489563388980582632159120621869165333921661377997970334407786581024278698231418756106787058054355713472306409772260619117725561889350862414726861327985706773512963177174611689685575805282
from Crypto.Util.number import *
import math
from sympy import *
f1, f2 = symbols("f1, f2")
f_solve = solve([f1 * f2 - n, f1 ** 4 + f2 ** 4 - s], [f1, f2])

print(f_solve)
p = 9886283652121924227364367891763650443585646023924602862402832944457976031272516261452668401020850156092802805016302198750132659632249261237412357024908843
q = 12744709820126371501672538820972733986675228838744099116170499394098621931788837908787562175058998307224041741444921422195431949471972242197577242845982421

phi = (p-1)*(q-1)
d = inverse(e,phi)
m = pow(c,d,n)
print(long_to_bytes(m))
# palu{This_is_a_fake_flag_change_it_for_real_use}

欧几里得

题目:

import random
import os
from Crypto.Util.number import *
from gmpy2 import lcm, gcd
def exgcd(a, b):
    if b == 0: return 1, 0
    x, y = exgcd(b, a%b)
    return y, x - a//b*y


def get_k():
    while True:
        p = getPrime(512)
        q = getPrime(512)
        phi = (p - 1) * (q - 1)
        if gcd(p * q, phi) == 1:
            break
    n = p * q
    while True:
        g = random.randint(1, n * n)
        if gcd((g - 1) // n, n) == 1:
            break
    return (n, g), (p, q)


def Paillier_encode(m, g, n):
    while True:
        r = random.randint(1, n - 1)
        if gcd(r, n) == 1:
            break
    return (pow(g, m, n * n) * pow(r, n, n * n)) % (n * n)


def Paillier_decode(c, p, q, g, n):
    lam = lcm(p - 1, q - 1)
    mi = exgcd((pow(g, lam, n * n) - 1) // n, n)[0] % n
    return (pow(c, lam, n * n) - 1) // n * mi % n


pk, sk = get_k()
n, g = pk
p, q = sk
m1 = bytes_to_long(flag)
m2 = bytes_to_long(os.urandom(2) * 35)
c1 = Paillier_encode(m1, g, n)
c2 = Paillier_encode(m2, g, n)
print(f'c = {Paillier_decode(c1 * c2, p, q, g, n)}')

# c = 1426774899479339414711783875769670405758108494041927642533743607154735397076811133205075799614352194241060726689487117802867974494099614371033282640015883625484033889861

由于Paillier 加密具有加法同态性,得到c=m1+m2c = m_1 + m_2
又因为是

m2 = bytes_to_long(os.urandom(2) * 35)

可以将m2m_2表示为m2=k(1+2562+2564++25668)m_2 = k* (1 + 256^2 + 256^4 + \dots + 256^{68})并且(0<k<256256)(0 < k < 256 *256)
使用等比数列求和公式

S=(1+2562+2564++25668=25670125621S = (1 + 256^2 + 256^4 + \dots + 256^{68}= \frac{256^{70} - 1}{256^2 - 1}

得到:

m1=ckSm_1 = c - k*S

爆破一下k就好了
exp:

from Crypto.Util.number import long_to_bytes

c = 1426774899479339414711783875769670405758108494041927642533743607154735397076811133205075799614352194241060726689487117802867974494099614371033282640015883625484033889861

S = (pow(256, 70) - 1) // (256**2 - 1)

# Brute-force k
for k in range(65536):
    m1 = c - k * S
    if m1 < 0:
        continue
    flag = long_to_bytes(m1)
    if flag.startswith(b'palu{'):
        print(f"Found k = {k}")
        print(f"Flag: {flag.decode()}")
        break
# palu{48b635a7a2474ef743e333478b67a2f5}

易如反掌

题目:

import gmpy2
from Crypto.Util.number import getPrime
import hashlib

primes = [(getPrime(1024), getPrime(1024)) for _ in range(4)]
N = [p * q for p, q in primes]
PHI = [(p**2 - 1) * (q**2 - 1) for p, q in primes]
d = getPrime(800)
flag = "palu{" + hashlib.md5(str(d)[].encode()).hexdigest() + "}"
E = [int(gmpy2.invert(d, PHI[i])) for i in range(4)]
print(N)
print(E)
# [23796646026878116589547283793150995927866567938335548416869023482791889761195291718895745055959853934513618760888513821480917766191633897946306199721200583177442944168533218236080466338723721813833112934172813408785753690869328477108925253250272864647989241887047368829689684698870160049332949549671046125158024445929082758264311584669347802324514633164611600348485747482925940752960745308927584754759033237553398957651216385369140164712159020014009858771182426893515016507774993840721603911101735647966838456333878426803669855790758035721418868768618171692143354466457771363078719423863861881209003100274869680348729, 19552522218179875003847447592795537408210008360038264050591506858077823059915495579150792312404199675077331435544143983146080988327453540449160493126531689234464110427289951139790715136775261122038034076109559997394039408007831367922647325571759843192843854522333120187643778356206039403073606561618190519937691323868253954852564110558105862497499849080112804340364976236598384571278659796189204447521325485338769935361453819608921520780103184296098278610439625935404967972315908808657494638735904210709873823527111315139018387713381604550946445856087746716671838144925662314348628830687634437271225081272705532826343, 20588310030910623387356293638800302031856407530120841616298227518984893505166480372963166394317326422544430837759332223527939420321960057410073228508230111170414845403161052128790464277007579491219950440477721075788978767309211469555824310913593208232853272958011299985202799390532181335087622499894389777412111445377637396650710486263652440053717323053536700098339137819966260269752816515681602936416736576044630343136577023173210517247609888936337876211461528203642347119434700140264859102502126842250671976238033270367185358966766106988830596616311824691409766437473419074865115209866730272194297815209976737570183, 18468380817178794606027384089796802449939260582378979728469492439450780893746976934315768186829245395964644992296264093276556001477514083927556578752836255491334765496791841945178275793885002188397918857222419803612711637177559554489679414049308077300718317502586411333302434329130562745942681716547306138457088216901181646333860559988117376012816579422902808478175975263110581667936249474308868051767856694498210084853797453949193117835061402537058150493808371384063278793041752943930928932275052745657700368980150842377283198946138726219378646040515809994704174471793592322237777371900834531014326150160506449286179]
# [229904181453273080302209653709086531153804577507365859149808244958841045687064628362978517491609413507875726243121473678430010600891588643092042173698830147997497783886459583186019270582236955524620567373560535686287255124958954671737097645556109314142383275516997850786599322033792080045303427363366927030304214333894247469120513426641296678531965795930756543043851154646310114366477311633838078242963665452936523438928643273392454483600446242320078010627755587492056369779661382734170244060951095344418599686788550312205964136120979823565225768814898285224838691541122088693411388097496320157113230752327025862802020421665288007529320920942060329299409362236414929126050037144149017275031336018100081931062647888329912802477032857776085190828105602067426203163344931483638271679183910241511044338001446584634203146294743522375846913845041274967653508735863706778364499099286484552570083394223973734909997825522191349543295855925973354640349809770822075226834555111927586299176453943116511915434890643239957459427390624136283086434711471863737451011157026905191204496081860277138227247744470804087252965368757930797560277881668806206419629425126031049566579233056222579590529869798537893505779097868221221068867624660759084762471141, 374749619911728044650812367560174497001343067563440477135516664935394734686391543012901514676044211541958613458868769659861216149364768233000844624035620893309356372294598009760824255187442531508754966566917198975934706398309982525100772311586501118200858124845012643495006029930202324305874402291277845166060497038915773767003006049720519011634861166208163030159519901867416488082395270295488885724507937683469910251316231210838654273986152493722244271430422693265608430755620420680629979226285393465423870727975987787149515374769359243334743541460110042872587610309611770320600248289328406805995688596910226273861759369388105641549933915686192055533242723330981192183310876306968103333706140401422550917946410378174896274789619184565321544130428008804628699594759946577979319393247067750024729672029363433673084437510430506410293512293930056667971242862448029841846596288648691077795207341975907335202945548990662460491169957175452745622341245617265849042542964819126377775749222973138584978725470886059043251544634105653274564085280013340679259157119014619894553239015777411757887293044706448625760604242512494466386343040583010961386979963779928616733980046763291988848903515836247301007113187121999960487508948748354549628160741, 111738429639840672983162926852338651562094139707285850255632987705635459657893186493838711733560515475806567653354737245246745810892238414756414117557971683747269900627524702653772058841085258035513296218047505149691384287812041721130367506731427022265277885965948486359682023555050085264531256406043361391744086539522028829421284667293339869140564699750714145488199268791908205712660933607330454849730499840287271163350865799682565216636393526339218836244889719975150503253630419647851422620890082315396457329065508602521784001607236788620811397449483104884860551374031790663030220424841642241965983726516537123807061999084476076850833658360594525986997125319941689903869138176347916707622148840226672408554102717625456819726220575710494929111642866840516339713870850732638906870325693572445316904688582043485093120585767903009745325497085286577015692005747499504730575062998090846463157669448943725039951120963375521054164657547731579771203443617489609201617736584055562887243883898406182052632245189418568410854530995044542628531851356363297989653392057214167031332353949367816700838296651167799441279086074308299608106786918676697564002641234952760724731325383088682051108589283162705846714876543662335188222683115878319143239781, 185935167438248768027713217055147583431480103445262049361952417166499278728434926508937684304985810617277398880507451351333771783039360671467147075085417403764439214700549777320094501151755362122677245586884124615115132430034242191429064710012407308619977881929109092467325180864745257810774684549914888829203014922855369708286801194645263982661023515570231007900615244109762444081806466412714045462184361892356485713147687194230341085490571821445962465385514845915484336766973332384198790601633964078447446832581798146300515184339036127604597014458389481920870330726947546808739829589808006774479656385317205167932706748974482578749055876192429032258189528408353619365693624106394913101463023497175917598944803733849984703912670992613579847331081015979121834040110652608301633876167262248103403520536210279949844194696898862249482809107840303473964914083996538912970715834110371196970613332286296427286356036576876121010776933023901744994067564045429384172315640135483480089769992730928266885675143187679290648773060781987273082229827156531141515679114580622348238382074084270808291251400949744720804368426414308355267344210055608246286737478682527960260877955900464059404976906697164610891962198768354924180929300959036213841843941]

根据这篇文章的格改了一下IJCSI-9-2-1-311-314.pdf

(Me0e1e2e3(N0)2(N1)2(N2)2(N2)2)\left( \begin{matrix} M & e_0 &e_1 & e_2 & e_3 & \\ & -(N_0)^2 & & & & \\ & &-(N_1)^2 & & & \\ & & &-(N_2)^2 & & \\ & & & &-(N_2)^2 & \\ \end{matrix} \right)

exp:

from Crypto.Util.number import *
import hashlib

n = [23796646026878116589547283793150995927866567938335548416869023482791889761195291718895745055959853934513618760888513821480917766191633897946306199721200583177442944168533218236080466338723721813833112934172813408785753690869328477108925253250272864647989241887047368829689684698870160049332949549671046125158024445929082758264311584669347802324514633164611600348485747482925940752960745308927584754759033237553398957651216385369140164712159020014009858771182426893515016507774993840721603911101735647966838456333878426803669855790758035721418868768618171692143354466457771363078719423863861881209003100274869680348729, 19552522218179875003847447592795537408210008360038264050591506858077823059915495579150792312404199675077331435544143983146080988327453540449160493126531689234464110427289951139790715136775261122038034076109559997394039408007831367922647325571759843192843854522333120187643778356206039403073606561618190519937691323868253954852564110558105862497499849080112804340364976236598384571278659796189204447521325485338769935361453819608921520780103184296098278610439625935404967972315908808657494638735904210709873823527111315139018387713381604550946445856087746716671838144925662314348628830687634437271225081272705532826343, 20588310030910623387356293638800302031856407530120841616298227518984893505166480372963166394317326422544430837759332223527939420321960057410073228508230111170414845403161052128790464277007579491219950440477721075788978767309211469555824310913593208232853272958011299985202799390532181335087622499894389777412111445377637396650710486263652440053717323053536700098339137819966260269752816515681602936416736576044630343136577023173210517247609888936337876211461528203642347119434700140264859102502126842250671976238033270367185358966766106988830596616311824691409766437473419074865115209866730272194297815209976737570183, 18468380817178794606027384089796802449939260582378979728469492439450780893746976934315768186829245395964644992296264093276556001477514083927556578752836255491334765496791841945178275793885002188397918857222419803612711637177559554489679414049308077300718317502586411333302434329130562745942681716547306138457088216901181646333860559988117376012816579422902808478175975263110581667936249474308868051767856694498210084853797453949193117835061402537058150493808371384063278793041752943930928932275052745657700368980150842377283198946138726219378646040515809994704174471793592322237777371900834531014326150160506449286179]

e0 = 229904181453273080302209653709086531153804577507365859149808244958841045687064628362978517491609413507875726243121473678430010600891588643092042173698830147997497783886459583186019270582236955524620567373560535686287255124958954671737097645556109314142383275516997850786599322033792080045303427363366927030304214333894247469120513426641296678531965795930756543043851154646310114366477311633838078242963665452936523438928643273392454483600446242320078010627755587492056369779661382734170244060951095344418599686788550312205964136120979823565225768814898285224838691541122088693411388097496320157113230752327025862802020421665288007529320920942060329299409362236414929126050037144149017275031336018100081931062647888329912802477032857776085190828105602067426203163344931483638271679183910241511044338001446584634203146294743522375846913845041274967653508735863706778364499099286484552570083394223973734909997825522191349543295855925973354640349809770822075226834555111927586299176453943116511915434890643239957459427390624136283086434711471863737451011157026905191204496081860277138227247744470804087252965368757930797560277881668806206419629425126031049566579233056222579590529869798537893505779097868221221068867624660759084762471141
n0 = n[0]

e1 = 374749619911728044650812367560174497001343067563440477135516664935394734686391543012901514676044211541958613458868769659861216149364768233000844624035620893309356372294598009760824255187442531508754966566917198975934706398309982525100772311586501118200858124845012643495006029930202324305874402291277845166060497038915773767003006049720519011634861166208163030159519901867416488082395270295488885724507937683469910251316231210838654273986152493722244271430422693265608430755620420680629979226285393465423870727975987787149515374769359243334743541460110042872587610309611770320600248289328406805995688596910226273861759369388105641549933915686192055533242723330981192183310876306968103333706140401422550917946410378174896274789619184565321544130428008804628699594759946577979319393247067750024729672029363433673084437510430506410293512293930056667971242862448029841846596288648691077795207341975907335202945548990662460491169957175452745622341245617265849042542964819126377775749222973138584978725470886059043251544634105653274564085280013340679259157119014619894553239015777411757887293044706448625760604242512494466386343040583010961386979963779928616733980046763291988848903515836247301007113187121999960487508948748354549628160741
n1 = n[1]

e2 = 111738429639840672983162926852338651562094139707285850255632987705635459657893186493838711733560515475806567653354737245246745810892238414756414117557971683747269900627524702653772058841085258035513296218047505149691384287812041721130367506731427022265277885965948486359682023555050085264531256406043361391744086539522028829421284667293339869140564699750714145488199268791908205712660933607330454849730499840287271163350865799682565216636393526339218836244889719975150503253630419647851422620890082315396457329065508602521784001607236788620811397449483104884860551374031790663030220424841642241965983726516537123807061999084476076850833658360594525986997125319941689903869138176347916707622148840226672408554102717625456819726220575710494929111642866840516339713870850732638906870325693572445316904688582043485093120585767903009745325497085286577015692005747499504730575062998090846463157669448943725039951120963375521054164657547731579771203443617489609201617736584055562887243883898406182052632245189418568410854530995044542628531851356363297989653392057214167031332353949367816700838296651167799441279086074308299608106786918676697564002641234952760724731325383088682051108589283162705846714876543662335188222683115878319143239781
n2 = n[2]

e3 = 185935167438248768027713217055147583431480103445262049361952417166499278728434926508937684304985810617277398880507451351333771783039360671467147075085417403764439214700549777320094501151755362122677245586884124615115132430034242191429064710012407308619977881929109092467325180864745257810774684549914888829203014922855369708286801194645263982661023515570231007900615244109762444081806466412714045462184361892356485713147687194230341085490571821445962465385514845915484336766973332384198790601633964078447446832581798146300515184339036127604597014458389481920870330726947546808739829589808006774479656385317205167932706748974482578749055876192429032258189528408353619365693624106394913101463023497175917598944803733849984703912670992613579847331081015979121834040110652608301633876167262248103403520536210279949844194696898862249482809107840303473964914083996538912970715834110371196970613332286296427286356036576876121010776933023901744994067564045429384172315640135483480089769992730928266885675143187679290648773060781987273082229827156531141515679114580622348238382074084270808291251400949744720804368426414308355267344210055608246286737478682527960260877955900464059404976906697164610891962198768354924180929300959036213841843941
n3 = n[3]

M = isqrt(n0)

L = Matrix(ZZ, [[M^2, e0, e1, e2, e3],
                [0,-n0^2,  0,  0,  0],
                [0,  0,-n1^2,  0,  0],
                [0,  0,  0,-n2^2,  0],
                [0,  0,  0,  0,-n3^2]])

d = abs(L.LLL()[0][0]) // M^2
flag = "palu{" + hashlib.md5(str(d).encode()).hexdigest() + "}"
print(flag)
# palu{b1fc01a38bae760451bcffe777e51b1d}

星际广播站

题目:

查看前端代码发现

const downloadUrl = `/file/download?path=${encodeURIComponent(filename)}`;

可以下载app.py

import sqlite3
import os
import hashlib
from flask import Flask, render_template, request, redirect, url_for, session, flash, g,send_file, abort
from Crypto.PublicKey import RSA
from Crypto.Util.number import bytes_to_long, getPrime
import string
from secret import flag    

app = Flask(__name__)
app.secret_key = os.urandom(24) # 用于 session 管理

# 修改数据库路径
# Ensure this line points to the data directory
DATABASE = 'data/users.db'
E = getPrime(7)
NUM_USERS = 128


# assert NUM_USERS >= E 
print(f"E: {E}")
FLAG = flag.encode('utf-8')

def get_db():
    db = getattr(g, '_database', None)
    if db is None:
        # Ensure the directory exists when connecting
        db_dir = os.path.dirname(DATABASE)
        if not os.path.exists(db_dir):
             os.makedirs(db_dir, exist_ok=True)
        db = g._database = sqlite3.connect(DATABASE)
        db.row_factory = sqlite3.Row # 让查询结果可以通过列名访问
    return db

@app.teardown_appcontext
def close_connection(exception):
    db = getattr(g, '_database', None)
    if db is not None:
        db.close()

import random
from gmssl import sm3, func 

def sm3_hash(data):
    """计算数据的 SM3 哈希值"""
    if isinstance(data, str):
        data = data.encode('utf-8')
    hash_bytes = sm3.sm3_hash(func.bytes_to_list(data))
    print(f"计算 {data} 的 SM3 哈希值为: {hash_bytes}")
    return hash_bytes

def generate_rsa_pair(message, e):
    """生成 RSA 公钥 N 和对应的密文 C"""
    key = RSA.generate(1024)
    n = key.n
    m_long = bytes_to_long(message)
    c = pow(m_long, e, n)
    return n, c

def init_db():
    """初始化数据库,创建表并填充用户数据"""
    db_path = os.path.join('/app', DATABASE) # 在容器内的绝对路径
    db_dir = os.path.dirname(db_path)
    os.makedirs(db_dir, exist_ok=True) # 确保容器内的目录存在

    # 检查数据库文件是否存在于容器内的预期路径
    if os.path.exists(db_path):
         print(f"数据库文件 {db_path} 已存在,跳过初始化。")
         # 即使文件存在,也要确保表结构是最新的
         # 可以考虑在这里添加检查表是否存在的逻辑,如果不存在则创建
         # return # 如果确定存在就跳过,否则继续执行建表逻辑

    print(f"初始化数据库 {db_path}...")
    with app.app_context():
        db = get_db()
        cursor = db.cursor()
        # 检查 users 表是否存在
        cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='users';")
        table_exists = cursor.fetchone()

        if not table_exists:
            print("创建 users 表...")
            cursor.execute('''
                CREATE TABLE users (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    username TEXT UNIQUE NOT NULL,
                    password_hash TEXT NOT NULL,
                    n TEXT NOT NULL -- 存储大整数 N
                )
            ''')
            db.commit() # 提交建表操作
        else:
            print("users 表已存在。")
            # 检查是否需要填充数据
            cursor.execute("SELECT COUNT(*) FROM users")
            user_count = cursor.fetchone()[0]
            if user_count >= NUM_USERS:
                print(f"数据库中已有 {user_count} 个用户,跳过填充。")
                return # 如果用户数量足够,则跳过填充

        # --- 填充用户数据的逻辑 ---
        print("开始填充用户数据...")
        generated_n_set = set()
        # 获取当前数据库中的用户数量
        cursor.execute("SELECT COUNT(*) FROM users")
        current_user_count = cursor.fetchone()[0]
        users_added_this_run = 0

        # 从 current_user_count + 1 开始生成用户,直到达到 NUM_USERS
        for i in range(current_user_count, NUM_USERS):
            username = str(i + 1)
            random.seed(username)

            characters = string.ascii_letters + string.digits
            password = "".join(random.choices(characters, k=6))
            password_hash = sm3_hash(password)

            n, c = None, None
            attempts = 0
            max_attempts = NUM_USERS * 5
            while attempts < max_attempts:
                n_candidate, c_candidate = generate_rsa_pair(FLAG, E)
                if n_candidate not in generated_n_set:
                    # 还需要检查数据库中是否已存在此 N
                    cursor.execute("SELECT 1 FROM users WHERE n = ?", (str(n_candidate),))
                    n_exists_in_db = cursor.fetchone()
                    if not n_exists_in_db:
                        n = n_candidate
                        c = c_candidate
                        generated_n_set.add(n)
                        break
                attempts += 1

            if n is None:
                print(f"警告:无法为用户 {username} 生成唯一的 N,已尝试 {max_attempts} 次。")
                continue

            try:
                cursor.execute("INSERT INTO users (username, password_hash, n) VALUES (?, ?, ?)",
                               (username, password_hash, str(n)))
                users_added_this_run += 1
                print(f"添加用户 {username} 到数据库。")
            except sqlite3.IntegrityError:
                print(f"用户名 {username} 已存在或 N 值冲突,跳过。")
            except Exception as e:
                print(f"添加用户 {username} 时出错: {e}")

        db.commit()
        print(f"数据库初始化/填充完成,本次运行添加了 {users_added_this_run} 个用户。")

@app.route('/')
def index():
    if 'username' in session:
        return redirect(url_for('dashboard'))
    return render_template('index.html') 

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        username = request.form['username']
        password_hash_attempt = sm3_hash(request.form['password_hash'])
        db = get_db()
        cursor = db.cursor()
        cursor.execute("SELECT password_hash FROM users WHERE username = ?", (username,))
        user = cursor.fetchone()

        if  user and user['password_hash'] == password_hash_attempt:
            session['username'] = username
            flash('登录成功!', 'success')
            return redirect(url_for('dashboard'))
        else:
            flash('无效的用户名或密码。', 'error')
            return redirect(url_for('login')) 
    

    if 'username' in session:
        return redirect(url_for('dashboard')) 
    return render_template('index.html')

@app.route('/dashboard')
def dashboard():
    if 'username' not in session:
        flash('请先登录。', 'error')
        return redirect(url_for('login'))

    username = session['username']
    db = get_db()
    cursor = db.cursor()
    cursor.execute("SELECT n  FROM users WHERE username = ?", (username,))
    user_data = cursor.fetchone()

    if not user_data:
        # 用户在 session 中但数据库中找不到?异常情况
        session.pop('username', None)
        flash('发生错误,请重新登录。', 'error')
        return redirect(url_for('login'))

    n = user_data['n']
#     c = user_data['c']
    m = bytes_to_long(FLAG)
    c = str(pow(m, E, int(n)))
    return render_template('dashboard.html', username=username, n=n, c=c, e=E)

@app.route('/logout')
def logout():
    session.pop('username', None)
    flash('您已成功登出。', 'success')
    return redirect(url_for('login'))



@app.route('/file/download', methods=['GET'])
def download_file():
    path = request.args.get('path', '')
    
    if not path:
        return "Error: No path parameter provided", 400
    
    try:

        if not os.path.isabs(path):
            path = os.path.join(os.path.dirname(__file__), path)
        
        if not os.path.exists(path) or not os.path.isfile(path):
            return f"Error: File not found: {path}", 404

        return send_file(
            path,
            as_attachment=True,
            download_name=os.path.basename(path),
            mimetype='application/octet-stream'
        )
    
    except Exception as e:
        return f"Error: {str(e)}", 500


if __name__ == '__main__':
    init_db() # 启动时检查并初始化数据库
    app.run(host='0.0.0.0', port=8000, debug=True)

发现

       for i in range(current_user_count, NUM_USERS):
            username = str(i + 1)
            random.seed(username)

            characters = string.ascii_letters + string.digits
            password = "".join(random.choices(characters, k=6))
            password_hash = sm3_hash(password)

seed就是用户名(0-128),那么就可以知道密码

import sqlite3
import os
import hashlib
import random
import string

from Crypto.PublicKey import RSA
from Crypto.Util.number import bytes_to_long, getPrime
password_1 = []
for i in range(1,128):
    username = str(i)
    random.seed(username)
    characters = string.ascii_letters + string.digits
    password = "".join(random.choices(characters, k=6))
    password_1.append(password)
    # print(password)
#     password_1 += (password)
#     # password_1 += password
print(password_1)


with open('./pass/pass.txt', "w") as f:
    i=1
    for password in password_1:
       f.write(str(i) + '|' + password+"\n")
       i = i+1

尝试用1当作用户名登陆

可以知道c和e
提取所有c

import requests
import bs4
# 打开并读取 pass.txt,每行格式为:账号|密码

with open("pass.txt", 'r') as file:
    c = []
    for line in file:
        line = line.strip()
        if not line:
            continue
        try:
            username, password_hash = line.split('|')
        except ValueError:
            print(f"[!] 格式错误:{line}")
            continue

        data = {
            'username': username,
            'password_hash': password_hash
        }

        url = 'http://challenge.qsnctf.com:31458/login'

        try:
            print(f"[*] 正在尝试登录用户 {data['username']}:{data['password_hash']}")
            response = requests.post(url, data=data)
            soup = bs4.BeautifulSoup(response.text, "html.parser")
            secret = soup.find('span', class_='data-value')
            print(secret.get_text(strip=True))
            c.append(secret.get_text(strip=True))
        except Exception as e:
            print(f"[!] 请求失败 (用户 {username}):{e}")
i = 1
with open('c.txt', 'w') as file:
    for ci in c:
        file.write(str(i) + '|' + ci + '\n')
        i += 1

并且知道n在data/users.db中,提取出来
那么就有129组的n和c,e使用广播攻击
exp:

from gmpy2 import *
from pwn import *
from Crypto.Util.number import *


def crt(n_list, c_list):
    n = 1
    for i in n_list:
        n *= i
    N = []
    for i in n_list:
        N.append(n // i)
    t = []
    for i in range(len(n_list)):
        t.append(invert(N[i], n_list[i]))

    summary = 0
    for i in range(len(n_list)):
        summary = (summary + c_list[i] * t[i] * N[i]) % n
    return summary


e = 109
n_list = []

c_list = []


M = crt(n_list, c_list)
m = iroot(M, e)[0]
flag = long_to_bytes(m)
print(flag)

写的时候没保存,后来发现的时候代码删了😭,所以就放一个简易版的吧

*文件查看器

题目:

源码:

from flask import Flask, render_template, request, redirect, url_for, session, flash, send_from_directory, make_response
import os
from gmssl import sm4


def xor(byte1, byte2):
    result = bytes(x ^ y for x, y in zip(byte1, byte2))
    return result


key = os.urandom(16)
iv = os.urandom(16)
sm4_encryption = sm4.CryptSM4()

app = Flask(__name__, template_folder='templates', static_folder='static')
app.secret_key = b'palupalupalupalupalupalupalupalupalu' 
# 保留原始的用户数据访问对象 (DAO)
class UserDAO(object):
    def __init__(self):
        self.users: dict[str, str] = {}

    def getPassword(self, uid: str) -> str:
        if uid not in self.users:
            raise Exception('用户不存在')
        return self.users[uid]

    def create(self, uid: str, password: str):
        if uid in self.users:
            raise Exception('用户已注册')
        self.users[uid] = password
        return

DAO = UserDAO()
DAO.create('demoUser', '123456')

# --- Flask 路由 --- 

@app.route('/')
def index():
    if 'uid' in session:
        return redirect(url_for('profile'))
    return redirect(url_for('login'))

@app.route('/register', methods=['GET', 'POST'])
def register():
    if request.method == 'POST':
        uid = request.form.get('uid')
        if 'admin' in uid:
            flash('不可以是admin哦', 'error')
            return redirect(url_for('register'))
        password = request.form.get('password')
        confirm_password = request.form.get('confirm_password')

        if not uid or not password:
            flash('用户ID和密码不可为空', 'error')
            return redirect(url_for('register'))

        if password != confirm_password:
            flash('两次输入的密码不一致', 'error')
            return redirect(url_for('register'))

        try:
            DAO.create(uid, password)
            flash('注册成功,请登录', 'success')
            return redirect(url_for('login'))
        except Exception as e:
            flash(str(e.args[0]), 'error')
            return redirect(url_for('register'))

    return render_template('register.html')

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        uid = request.form.get('uid')
        password = request.form.get('password')

        if not uid or not password:
            flash('用户ID和密码不可为空', 'error')
            return redirect(url_for('login'))

        try:
            stored_password = DAO.getPassword(uid)
            if stored_password != password:
                raise Exception('用户ID或密码错误')

            user_level = 'admin' if uid == 'admin' else 'guest'
            
            sm4_encryption.set_key(key, sm4.SM4_ENCRYPT)
            token_payload = f"{uid}:{user_level}".encode('utf-8')
            token = sm4_encryption.crypt_cbc(iv, token_payload).hex()

            session['uid'] = uid
            flash(f'登录成功,您的 token 是: {token}', 'success')
            # 创建响应对象并设置 cookie
            response = make_response(redirect(url_for('profile')))
            response.set_cookie('auth_token', token, httponly=True, samesite='Lax') # 设置 cookie
            return response

        except Exception as e:
            flash(str(e.args[0]), 'error') # 显示更具体的错误信息
            return redirect(url_for('login'))

    return render_template('login.html')

@app.route('/profile')
def profile():
    if 'uid' not in session:
        return redirect(url_for('login'))
    
    # 可以在这里添加解密 token 显示信息的逻辑,但暂时只显示用户名
    username = session.get('uid')
    return render_template('profile.html', username=username)

@app.route('/logout')
def logout():
    session.pop('uid', None)
    flash('您已成功登出', 'info')
    return redirect(url_for('login'))

@app.route('/file', methods=['GET', 'POST'])
def read_file_page():
    if 'uid' not in session :
        flash('请先登录', 'warning')
        return redirect(url_for('login'))
    print(session)
    
    file_content = None
    error_message = None
    file_path_requested = ''

    if request.method == 'POST':
        token = request.cookies.get('auth_token') # 从 cookie 获取 token
        file_path = request.form.get('path')
        file_path_requested = file_path # 保留用户输入的路径以便回显
        if not file_path:
            error_message = '路径不可为空'
        else:
            try:
                # 保留原始的 SM4 CBC 令牌验证逻辑
                sm4_encryption.set_key(key, sm4.SM4_DECRYPT)
                token_decrypted = sm4_encryption.crypt_cbc(iv, bytes.fromhex(token))
                decrypted_str = token_decrypted.decode('utf-8', errors='ignore') 
                parts = decrypted_str.split(':')[-2:]

                uid_from_token, lv = parts

                if 'admin' in lv:
                    print(f"管理员 {uid_from_token} 尝试读取: {file_path}")
                    try:

                        with open(file_path, 'r', encoding='utf-8') as f:
                            file_content = f.read()
                    except FileNotFoundError:
                        error_message = '文件未找到'
                    except Exception as e:
                        print(f"读取文件错误: {e}")
                        error_message = '读取文件失败'
                else:
                    error_message = '非管理员,无权限读取服务器文件'
            except ValueError as e:
                 error_message = f'token非法: {e}'
            except Exception as e:
                print(f"Token 解密/验证错误: {e}")
                error_message = 'token无效或已过期'

    if error_message:
        flash(error_message, 'error')
        
    return render_template('file_viewer.html', file_content=file_content, file_path_requested=file_path_requested)

# 移除 Flask-RESTX 的 404 处理,使用 Flask 默认或自定义模板
@app.errorhandler(404)
def page_not_found(e):
    # 可以创建一个 templates/404.html 页面
    return render_template('404.html'), 404 

if __name__ == '__main__':
    # 确保 static 文件夹存在
    if not os.path.exists('static'):
        os.makedirs('static')
    # 确保 templates 文件夹存在
    if not os.path.exists('templates'):
        os.makedirs('templates')
    # 404 页面
    if not os.path.exists('templates/404.html'):
        with open('templates/404.html', 'w', encoding='utf-8') as f:
            f.write('<!doctype html><title>404 Not Found</title><h1>页面未找到</h1><p>您访问的页面不存在。</p><a href="/">返回首页</a>')

    if not os.path.exists('static/style.css'):
         with open('static/style.css', 'w', encoding='utf-8') as f:
            f.write('''
body { font-family: sans-serif; margin: 20px; background-color: #f4f4f4; }
.container { max-width: 600px; margin: auto; background: #fff; padding: 20px; border-radius: 8px; box-shadow: 0 0 10px rgba(0,0,0,0.1); }
h1 { color: #333; }
.form-group { margin-bottom: 15px; }
label { display: block; margin-bottom: 5px; }
input[type="text"], input[type="password"] { width: calc(100% - 22px); padding: 10px; border: 1px solid #ddd; border-radius: 4px; }
button { background-color: #5cb85c; color: white; padding: 10px 15px; border: none; border-radius: 4px; cursor: pointer; }
button:hover { background-color: #4cae4c; }
a { color: #0275d8; text-decoration: none; }
a:hover { text-decoration: underline; }
.flash-messages { margin-bottom: 15px; }
.alert { padding: 10px; border-radius: 4px; margin-bottom: 10px; }
.alert-error { background-color: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; }
.alert-success { background-color: #d4edda; color: #155724; border: 1px solid #c3e6cb; }
.alert-info { background-color: #d1ecf1; color: #0c5460; border: 1px solid #bee5eb; }
.alert-warning { background-color: #fff3cd; color: #856404; border: 1px solid #ffeeba; }
.file-content { margin-top: 20px; padding: 15px; background-color: #eee; border: 1px solid #ddd; border-radius: 4px; white-space: pre-wrap; word-wrap: break-word; }
nav { margin-bottom: 20px; text-align: right; }
nav span { margin-right: 15px; }
            ''')

    app.run(debug=True, host='0.0.0.0', port=10002)

发现使用的CBC模式加密

sm4_encryption.set_key(key, sm4.SM4_ENCRYPT)
token_payload = f"{uid}:{user_level}".encode('utf-8')
token = sm4_encryption.crypt_cbc(iv, token_payload).hex()

其实就是字节反转攻击,修改user_level,从pad(':guest')修改为pad(':admin')
赛时一直在改用户名,咋搞都不对
直接用下ChaMd5的wp
exp:

BLOCK_SIZE = 16

def pkcs7_pad(data, block_size):
    padding_len = block_size - (len(data) % block_size)
    padding = bytes([padding_len] * padding_len)
    return data + padding


def xor_bytes(b1, b2):
   return bytes(x ^ y for x, y in zip(b1, b2))


auth_token = '7300fbdc1922432112a7741aa4daf9829729137db4b167b46f4fa1ea396266c7'
A = auth_token[:32]
C_old = pkcs7_pad(':guest'.encode(), BLOCK_SIZE)
B = xor_bytes(bytes.fromhex(A), C_old)
C_new = pkcs7_pad(':admin'.encode(), BLOCK_SIZE)
new_token = xor_bytes(B, C_new).hex() + auth_token[32:]
print(new_token)
#7306ead40338432112a7741aa4daf9829729137db4b167b46f4fa1ea396266c7

剩下两道题,太脑洞了,就不写题解了