תזכורת מ curl - מי ביקש ממך?

12/10/2023

הבאג שנחשף אתמול ב libcurl הוא כזה שמתכנתים אוהבים לעשות ואוהבים להתחרט עליו. היוצר של curl דניאל סטנברג פרסם את הפרטים. בגדול כשמושכים דף דרך libcurl אפשר להשתמש בפרוקסי, ואם משתמשים בפרוקסי אפשר לבחור לפענח לבד את שם השרת אליו פונים ולתת לפרוקסי את כתובת ה IP, או להעביר את שם השרת לפרוקסי שהוא ישבור את הראש.

יש לו גם משתנה בשם socks_proxy.proxytype שיכול לקבל את הערך CURLPROXY_SOCKS5 בשביל לפענח לבד את כתובת ה IP אליה צריך להתחבר, או את הערך CURLPROXY_SOCKS5_HOSTNAME בשביל להעביר את שם השרת ישירות לפרוקסי שהוא כבר ישבור את הראש. אבל במצב שצריך להעביר את שם השרת יש בעיה - שרתי socks תומכים רק בשמות שרת באורך עד 255 תווים.

עכשיו מה הייתם עושים אם משתמש בחר בשיטת עבודה לחיבור "שלח את שם השרת לפרוקסי" אבל שם השרת שהוא מנסה להתחבר אליו יצא ארוך יותר ממה שמתאפשר לשלוח?

דניאל עשה מה שרבים מאיתנו היו עושים ומתחרטים עליו בדיעבד - ברגע שהוא ראה שם שרת ארוך הוא הניח שמישהו העביר בטעות את הערך הלא נכון לפרמטר שבוחר מצב עבודה, ושינה את מצב העבודה לפיענוח שם שרת מקומי.

הבעיה שבגלל מבנה הקוד, השינוי לא משפיע על כל המצבים ואפשר לעקוף אותו. תוקף זדוני שרוצה להפיל את ה curl שלכם יכול לבנות שרת שישלח Redirect עם שם שרת ארוך מדי ובמצבים מסוימים יעקוף את שינוי מצב העבודה ויגרום ל Heap Overflow בקוד הלקוח.

הקוד הפגיע בגדול הוא:

  bool socks5_resolve_local =
    (conn->socks_proxy.proxytype == CURLPROXY_SOCKS5) ? TRUE : FALSE;
  const size_t hostname_len = strlen(sx->hostname);
  ssize_t len = 0;
  const unsigned char auth = data->set.socks5auth;
  bool allow_gssapi = FALSE;
  struct Curl_dns_entry *dns = NULL;

  DEBUGASSERT(auth & (CURLAUTH_BASIC | CURLAUTH_GSSAPI));
  switch(sx->state) {
  case CONNECT_SOCKS_INIT:
    if(conn->bits.httpproxy)
      infof(data, "SOCKS5: connecting to HTTP proxy %s port %d",
            sx->hostname, sx->remote_port);

    /* RFC1928 chapter 5 specifies max 255 chars for domain name in packet */
    if(!socks5_resolve_local && hostname_len > 255) {
      infof(data, "SOCKS5: server resolving disabled for hostnames of "
            "length > 255 [actual len=%zu]", hostname_len);
      socks5_resolve_local = TRUE;
    }

וזה פגיע כי שינוי הערך של המשתנה הבוליאני קורה רק במקרה של CONNECT_SOCKS_INIT, אבל אפשר להיכנס לפונקציה הזאת גם ישירות ל case-ים אחרים במצבים של שרת פרוקסי איטי.

המסקנה של דניאל נכונה גם לגבינו ולכל קוד שאנחנו כותבים. הוא כותב:

curl should not switch mode from remote resolve to local resolve due to too long host name. It should rather return an error and starting in curl 8.4.0, it does.

החלפת מצב הפעולה רק בגלל שגילינו ב init ששם השרת ארוך מדי היתה הטעות, כי משתמשים שביקשו מצב פעולה מסוים לא ציפו להתנהגות זו. זריקת שגיאה במקרה כזה היתה נותנת למשתמשים אפשרות לבחון טוב יותר את ה use case ולבחור את מצב הפעולה הטוב ביותר עבורם.