{"id":37126487,"url":"https://github.com/mwarzynski/confidence2019_teaser_lottery","last_synced_at":"2026-01-14T14:37:05.041Z","repository":{"id":80165150,"uuid":"175484277","full_name":"mwarzynski/confidence2019_teaser_lottery","owner":"mwarzynski","description":"'The Lottery' challenge at 'Teaser CONFidence CTF 2019'.","archived":false,"fork":false,"pushed_at":"2023-04-13T13:06:09.000Z","size":105,"stargazers_count":5,"open_issues_count":0,"forks_count":2,"subscribers_count":1,"default_branch":"master","last_synced_at":"2024-06-21T18:04:03.802Z","etag":null,"topics":["ctf-challenges","golang","slices","vulnerability"],"latest_commit_sha":null,"homepage":"https://ctftime.org/event/760/","language":"Go","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/mwarzynski.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-03-13T19:17:16.000Z","updated_at":"2023-04-13T13:08:11.000Z","dependencies_parsed_at":null,"dependency_job_id":"c6b79e75-ae15-4558-9088-6207f116bebe","html_url":"https://github.com/mwarzynski/confidence2019_teaser_lottery","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/mwarzynski/confidence2019_teaser_lottery","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mwarzynski%2Fconfidence2019_teaser_lottery","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mwarzynski%2Fconfidence2019_teaser_lottery/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mwarzynski%2Fconfidence2019_teaser_lottery/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mwarzynski%2Fconfidence2019_teaser_lottery/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mwarzynski","download_url":"https://codeload.github.com/mwarzynski/confidence2019_teaser_lottery/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mwarzynski%2Fconfidence2019_teaser_lottery/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28423955,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-14T13:30:50.153Z","status":"ssl_error","status_checked_at":"2026-01-14T13:29:08.907Z","response_time":107,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["ctf-challenges","golang","slices","vulnerability"],"created_at":"2026-01-14T14:37:04.220Z","updated_at":"2026-01-14T14:37:04.940Z","avatar_url":"https://github.com/mwarzynski.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# The Lottery\n\nThe **last solved** challenge at 'Teaser CONFidence CTF 2019'.\n\n## Logic\n\nThe challenge is a REST API written in Go. It has a functionality of choosing\na lottery winner along with the accounts *management*.\n\n### Package `app`\n\nAccount:\n```go\ntype Account struct {\n\tName    string `json:\"name\"`\n\tAmounts []int  `json:\"amounts\"`\n}\n```\n\nBasically, we may do two things with the account:\n - `Account.AddAmount(50)` adds provided amount (must be wihtin `0-99`),\n        also total number of amounts must be less than 5,\n - `Service.LotteryAdd(account)` signs user up to the lottery.\n\nYou have two options to get a flag - your account must:\n 1. Be a millionaire (sum of amounts \u003e 1,000,000),\n 2. Be a lottery's winner.\n\n### Package `transport`\n\nThere is implementation of HTTP handlers and routing. All we need to know is:\nflag is served with the account of millionaire or lottery winner.\n\n## Vulnerabilities\n\nLet's take a look how exactly `lottery` works.\n\nWhen we invoke `Service.LotteryAdd` method it passes the copy of `account`\nobject to lottery. It means we have the same object (literally bytes) in two\nplaces: `lottery` (copy) and `service` (original).\n\n\nThe next step: `lottery.evaluate` loops over previously copied accounts and does:\n```go\namounts := append(account.Amounts, randInt(999913, 3700000))\n```\n\nPretty cool. It adds a really big number (~1m) to `account.Amounts`. If only it\ncould be saved at the 'original' account too...\n\nThen it checks if total sum of amounts and our random number is equal to\n`1259264`.\n\n### Solution 1 (brute force)\n\nRegardless of added amounts to account, we have only one value that wins the\nlottery (in range `999913-3700000`).\n\n```python\n\u003e\u003e\u003e 3700000 - 999913\n2700087\n```\n\nTheoretically, to get a flag you may just create accounts and after\n`LOTTERY_PERIOD` duration check if this user is a lottery winner.\n\nFor each try we must do three HTTP requests:\n 1. Create an account,\n 2. Add account to lottery,\n 3. After `LOTTERY_PERIOD` duration check if the account is a winner.\n\n Let's do a quick math:\n```python\n\u003e\u003e\u003e 2700087*3        # total number of requests\n8100261\n\u003e\u003e\u003e 8100261 / 2500   # divide by 2500 rps (are you capable of doing more?)\n3240.1044\n\u003e\u003e\u003e 3240.1044 / 3600 # how many hours? \n0.90\n```\n\nAs you can see, with a high confidence it was possible to get a flag within one hour!\nIt wasn't intended solution, but probably some teams managed to use it.\nCongratulations though.\n\nFor more information, you can read the CTF Post Mortem: https://github.com/p4-team/ctf/tree/master/2019-03-17-confidence2019#4-monitoring\n\n### Solution 2 (go slices)\n\nLet's go back to previously noticed amounts `append` with a big number.\nSo, how exactly does it work?\n\nWe append an `Account`'s attribute which is a `[]int` slice. \n```go\ntype Account struct {\n\tAmounts []int // This is a []int slice.\n}\n```\nHowever, what's a slice? (Worth reading: https://blog.golang.org/go-slices-usage-and-internals)\n\nDefinition of slice:\n\u003e A slice is a descriptor of an array segment. It consists of a pointer to the\n\u003e array, the length of the segment, and its capacity (the maximum length of\n\u003e the segment).\n\n![Slice visualization](images/go-slices-usage-and-internals_slice-struct.png)\n\nIn fact, a slice is a pointer to the memory and not the memory itself! It means\nwe are able to write to the *original* `amounts` memory while the lottery evaluation.\nHowever, slice is kind of like the `vector` in C++. If you will append the\nelement over the available capacity, it will reallocate itself to the another\nbigger memory chunk. Therefore we must ensure, reallocation won't happen.\n\nOur strategy is creating two slices which point to the same memory. We copy\n`Account` struct when adding to the `Lottery`, so the slice will be also\ncopied. When it comes to the capacity - we need to have such length and\ncapacity that `append` won't reallocate the slice (and just write to the\nmemory). `MaxAmountsLen = 4`, so we may exploit it by `capacity=4` and\n`length=3`. In this case adding another item will just overwrite fourth element.\n\nAnyway, here is the code:\n```go\nfunc HackLottery() {\n  // Create new service.\n  s := NewService(context.Background(), time.Millisecond, time.Minute)\n\n  // Add new account.\n  a, _ := s.AccountAdd()\n\n  // Add '90' amount three times.\n  s.AccountAddAmount(a.Name, 90)\n  s.AccountAddAmount(a.Name, 90)\n  s.AccountAddAmount(a.Name, 90)\n\n  // Right now we have a slice with three values [90, 90, 90]\n  // Do you know what's the capacity and length of it?\n  // Length=3, Capacity=4.\n  // It means that after the next 'append' we won't reallocate to the new memory.\n\n  // We would like to extend our original slice to length=4 and then wait for\n  // the lottery's append to overwrite the last item.\n\n  // Add account to the 'Lottery'.\n  s.LotteryAdd(a.Name)\n  // Immediately after extend 'amounts' slice.\n  s.AccountAddAmount(a.Name, 90)\n\n  // 'Lottery.evaluate' should overwrite the last element in the original amounts.\n  s.lottery.evaluate()\n\n  // Does it work?\n  a, _, _ = s.AccountGet(a.Name)\n  fmt.Printf(\"%v\\n\", a.Amounts)\n  // [90 90 90 1723342]\n  // Yay, you are a millionaire.\n}\n```\n\nDo you know why it doesn't work for slices with capacity less than 4?\n\n## Flag\n\n```sh\n$ python hack.py       \np4{fucking-go-slices.com}\n```\n\n## Survey\n\n![Which challenge was the coolest](images/bestchall.png)\n\nAccordingly to the poll, 'The Lottery' was the second coolest challenge!\nThank you.\n\n[https://github.com/p4-team/ctf/tree/writeup/2019-03-17-confidence2019](https://github.com/p4-team/ctf/tree/master/2019-03-17-confidence2019)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmwarzynski%2Fconfidence2019_teaser_lottery","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmwarzynski%2Fconfidence2019_teaser_lottery","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmwarzynski%2Fconfidence2019_teaser_lottery/lists"}