סיכום סדנא: מתקפת Hash Length Extension
אתמול התקיימה סדנת אבטחת מידע בנושא מתקפה קצת מוזרה שנקראת Hash Length Extension. אחד המופעים הראשונים של המתקפה היה כש Flickr העלו API הפגיע למתקפה זו ב 2009. בסדנא דיברנו על המתקפה ואיך להשתמש נכון בפונקציות Hash ואם אתם מנויים מוזמנים לצפות בהקלטה. למי שלא אסכם כאן את עיקרי הדברים.
1. מנגנון אימות הודעה
מנגנון אימות הודעה הוא מנגנון שיאפשר לנו לוודא שהודעה מסוימת הגיעה ממי שאנחנו חושבים שהיא הגיעה ממנו. במקרה של Flickr היה להם מאוד חשוב לוודא שהודעה שהגיעה דרך ה API הגיעה מלקוח אמיתי של ה API. דוגמא נוספת היא Cookies שבמערכות מבוזרות אנחנו אוהבים לשמור בהן מידע רגיש ולוודא שלקוח לא שינה את המידע לפני הפניה הבאה לשרת.
יש די הרבה מנגנונים לאימות הודעות ובסדנא דיברנו על מנגנון שנקרא HMAC. במנגנון זה אנחנו מחשבים ערך Hash של הודעה בצירוף מידע סודי ונותנים את הערך ללקוח בתור "חתימה". כשנקבל את ההודעה חזרה נוכל לצרף אליה את המידע הסודי ולחשב שוב את ערך ה Hash ולראות אם הערך זהה למה שהלקוח העביר כנראה שההודעה לא השתנתה.
2. איך לא לבנות מנגנון אימות הודעה - דוגמת שרת
בהנחה ש SECRET הוא משתנה שמכיל מידע סודי ו data הוא ההודעה המקורית, מנגנון אימות ההודעות השבור ש Flickr השתמשו בו נראה בערך כך:
sig = MD5(SECRET || data)
כלומר מחברים את המידע הגלוי למידע הסודי ומחשבים ערך MD5 על שתי המילים המשורשרות.
בסדנא ראינו קוד צד שרת בפייתון שמשתמש באותו מנגנון:
import hashlib
import secrets
from flask import Flask, request, jsonify
import urllib.parse
app = Flask(__name__)
SECRET = secrets.token_hex(4).encode('latin-1')
@app.route("/")
def hello():
parts = request.query_string.split(b'&')
for p in parts:
name, val = p.split(b'=')
if name == b'data':
data = urllib.parse.unquote_to_bytes(val)
elif name == b'sig':
sig = val
expected_sig = hashlib.md5(SECRET + data).hexdigest().encode('latin-1')
if expected_sig == sig:
p = {w.split(b'=')[0]: w.split(b'=')[1] for w in data.split(b'&')}
if int(p[b'uid']) == 1:
return "Welcome Master. The secret key is: {}\n".format(SECRET)
else:
return "Welcome User\n"
print('expected sig = {}'.format(expected_sig))
print('got sig = {}'.format(sig))
print('got data = {}'.format(data))
return "INTRUDER ALERT\n"
@app.route("/tip")
def tip():
data = b'uid=0'
return jsonify({
'data': data,
'sig': hashlib.md5(SECRET + data).hexdigest(),
})
if __name__ == '__main__':
app.run()
מסתבר שקל מאוד לגרום לשרת הזה לתת לנו את המידע הסודי שלו באמצעות מתקפת Hash Length Extension.
3. מה כן עובד
בפייתון אפשר לייצר קוד אימות על הודעה בצורה נכונה באמצעות מנגנון HMAC שכבר קיים בשפה ובמקומות רבים נוספים. הרעיון שבמקום לחשב Hash רק על ההודעה והמפתח נעשה משהו יותר מתוחכם שנראה בערך כך:
H(k || H(k || m))
מחשבים את ה Hash פעמיים: פעם אחת על ההודעה והמפתח ופעם שניה על המפתח עם ה Hash הראשון. חישוב זה מגן עלינו מחולשות של פונקציות Hash. קוד פייתון שמחשב HMAC עם המפתח הסודי secret ועל המידע uid=0 יראה כך:
hmac.new(b'secret', b'uid=0').hexdigest()