{"id":16195289,"url":"https://github.com/tethik/ructfe-radio-sploit","last_synced_at":"2025-04-07T15:54:39.427Z","repository":{"id":146621895,"uuid":"223641692","full_name":"Tethik/ructfe-radio-sploit","owner":"Tethik","description":"Quick Writeup / Dump of my RuCTFE 2019 Radio Exploit","archived":false,"fork":false,"pushed_at":"2019-11-23T22:54:36.000Z","size":10845,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-02-13T18:37:19.425Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Tethik.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-11-23T19:26:33.000Z","updated_at":"2019-11-23T22:54:38.000Z","dependencies_parsed_at":"2023-04-19T03:04:41.517Z","dependency_job_id":null,"html_url":"https://github.com/Tethik/ructfe-radio-sploit","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Tethik%2Fructfe-radio-sploit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Tethik%2Fructfe-radio-sploit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Tethik%2Fructfe-radio-sploit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Tethik%2Fructfe-radio-sploit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Tethik","download_url":"https://codeload.github.com/Tethik/ructfe-radio-sploit/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247685594,"owners_count":20979084,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-10T08:26:57.703Z","updated_at":"2025-04-07T15:54:39.403Z","avatar_url":"https://github.com/Tethik.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# RUCTFE 2019 Radio Exploit / Writeup\n\nThis was the exploit I used to capture flags from the Radio service.\nI noticed early on that the JWT Secret was generated in a predictable way. The following script was\nused to generate the secret key used to sign the JWT.\n\n```bash\n#!/bin/bash\nset -e\n\ngenerate_key() {\n    declare -r key_path=$1\n    echo \"$(date +\\\"%s\\\")$key_path\"\n    echo -n \"$(date +\\\"%s\\\")$key_path\" | sha256sum | awk '{print $1}' \u003e $key_path\n}\n\ndeclare -r jwt_secret_path=\"${SECRET_PATH%/}/jwt_secret\"\ndeclare -r session_key=\"${SECRET_PATH%/}/session_key\"\n\ngenerate_key $jwt_secret_path\ngenerate_key $session_key\n```\n\nProblem here is the `\"$(date +\\\"%s\\\")$key_path\"` that is fed to sha256sum. It's easy to bruteforce too, only requiring a few thousand offline attempts.\n\nIn turn the code that generated the actual JWT was this module, which suspiciously called a `radio-auth.so` plugin. This was where the above secret was passed in to.\n```go\npackage auth\n\nimport (\n\t\"fmt\"\n\t\"plugin\"\n)\n\nconst pluginName = \"./radio-auth.so\"\n\ntype EncodeFn = func(interface{}) string\ntype DecodeFn = func(string, interface{}) error\ntype InitAuthFn = func(string)\n\nvar Encode EncodeFn\nvar Decode DecodeFn\n\nfunc InitAuth(init string) (err error) {\n\tvar plug *plugin.Plugin\n\tif plug, err = plugin.Open(pluginName); err != nil {\n\t\treturn\n\t}\n\tvar encodeFnSym, decodeFnSym, initAuthSym plugin.Symbol\n\tif encodeFnSym, err = plug.Lookup(\"Encode\"); err != nil {\n\t\treturn\n\t}\n\tif decodeFnSym, err = plug.Lookup(\"Decode\"); err != nil {\n\t\treturn\n\t}\n\tvar ok bool\n\tif Encode, ok = encodeFnSym.(EncodeFn); !ok {\n\t\treturn fmt.Errorf(\"Can't read encode function from auth module\")\n\t}\n\tif Decode, ok = decodeFnSym.(DecodeFn); !ok {\n\t\treturn fmt.Errorf(\"Can't read encode function from auth module\")\n\t}\n\tif initAuthSym, err = plug.Lookup(\"InitAuth\"); err != nil {\n\t\treturn\n\t}\n\tvar initFn InitAuthFn\n\tif initFn, ok = initAuthSym.(InitAuthFn); !ok {\n\t\treturn fmt.Errorf(\"Can't read InitAuth function from auth module\")\n\t}\n\tinitFn(init)\n\treturn\n}\n```\n\nUsing https://jwt.io/ I saw that the JWT generated had a weird \"alg\" field set to `42`. So there was clearly something fishy going on there.\n\nTo patch I initially changed the secret where it was generated by just adding a random string.\n\nThe exploit then ran as follows:\n1. Register new user (`/frontend-api/register/`), log in (`/frontend-api/login/`) and create a api token (`/api/v1/token/`). I pipelined this to all hosts with the `register.py` script which I ran once to a list of JWT tokens for every host responding.\n2. Take the API token, and bruteforce it with the `radio-auth.so` plugin. I wrote the `crack` golang program for this. I was surprised how quick it was to bruteforce all ~103 hosts. With this I have a list of each service + secret used to sign the JWT using `radio-auth.so`.\n3. With the now known secret+hosts, my attack was to visit the `/frontend-api/our-users/` route, get the last 20 users, then for each user spoof a JWT with the `sign`-golang and get their playlists from `/api/v1/playlist/`. If I was lucky, there would be a flag in the playlist description. The `exfil.py` combined all the steps and would submit flags if found. \n\nProbably it would have been smart to reverse `radio-auth.so` and figure out what it actually does. I'm not confident in my reversing skills though, so I thought this would take too long.\n\nBecause we were still getting pwned, to mitigate it fully I replaced the entire JWT signing/parsing mechanism. This took a bit too long though, and most likely they were using another vulnerability.\n\nThe only optimization I did here was to pipeline the process. In the end I just ran 4 `exfil.py` worker scripts on different target lists. \n\n## Another vuln\n\nOur service still continued to get exploited though, I'm pretty sure the following \"share hash\" on the playlist was abused. Also what I saw a lot of spam too in the logs. In the very last minute I uploaded a fix which added a salt though. It's the thought that matters.\n\n```go\nfunc (p *Playlist) HS() string {\n\treturn fmt.Sprintf(\"%x\", sha256.Sum256([]byte(fmt.Sprintf(\"playlist:{%d}:{%t}:{%d}:{\u0026b}\", p.ID, p.Private, p.UserID))))\n}\n```\n\n## Learnings\nSince this was my first A/D CTF, I learned some things which I could improve or prepare for in the next competition:\n\n1. Try to avoid tunnelvision. I spent most of the time focused on my own vuln while I missed the probably more obvious one. We lost more flags than we stole from this. Patch first, then exploit.\n2. Do be a bit stubborn though, I feel like my focus here was worthwhile as I did still score an ok amount of points.\n3. Come prepared with a way to enumerate exploits over many hosts at once. This would have saved me a ton of time and would have scored us a lot more points. \n5. Do figure out what the exact rules are for defense. We had some very noisy neighbours that were bringing down our services. I wasn't sure if we were allowed to straight up block opponents or rate limit or delete old data from the databases. What are our options here?\n6. Probably interact more with the team. If we as a team were more prepared we could have likely delegated to different roles once a vulnerability was discovered. E.g. one person does exploit dev and another does patching. Maybe a third watches the logs. However our team was small and generally we were just doing it for fun, so I think we did ok.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftethik%2Fructfe-radio-sploit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftethik%2Fructfe-radio-sploit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftethik%2Fructfe-radio-sploit/lists"}