אבטחה ומצב taint#
Security in a Perl program is mostly about not trusting data that came from outside the program: command-line arguments, the environment, file contents, network input. The dangerous step is not reading that data but acting on it. A filename that arrives from a web form is harmless until it reaches open; an environment variable is harmless until it steers a system call. This page describes the mechanism PetaPerl gives you to make that boundary explicit: taint mode. It explains what taint mode is, when it turns on, what it marks as untrusted, and how data is cleaned.
מצב taint#
PetaPerl runs the perl5 5.42 core, so taint mode is the real perl5 taint machinery, not an approximation. Every check perl5 performs, PetaPerl performs.
מצב taint נדלק אוטומטית כאשר PetaPerl מזהה שהיא רצה עם מזהי משתמש או קבוצה ממשיים ואפקטיביים שונים, וכך מורץ סקריפט setuid (מצב Unix 04000) או setgid (מצב 02000). תוכל גם להפעיל אותו במפורש באמצעות מתג שורת הפקודה -T. -T מומלץ בחום עבור כל תוכנית שרצה מטעם מישהו אחר, כאשר סקריפט CGI הוא המקרה הקלאסי. לאחר שמצב taint דלוק, הוא נשאר דלוק עד סוף התוכנית; לא ניתן לכבותו מבפנים.
המתג חייב להופיע מוקדם מספיק כדי שהמפרש יראה אותו לפני שיתחיל לפרש את תוכניתך. בשורת ה-#! של סקריפט זה קורה אוטומטית. בשורת הפקודה, מקם אותו בין המתגים הראשונים. המתג הקרוב -t מפעיל את אותן הבדיקות אך מוריד את השגיאות הקטלניות לאזהרות, מה שמועיל בעת התאמת מצב taint לתוכנית קיימת: אתה רואה כל מקום שהיה נכשל בלי שהתוכנית תמות בראשון שבהם.
המשתנה לקריאה בלבד ${^TAINT} מדווח על המצב הנוכחי:
| משמעות |
|---|---|
| מצב taint דלוק, קטלני ( |
| בדיקות taint דלוקות, אזהרה בלבד ( |
| מצב taint כבוי |
מה נעשה tainted#
כאשר מצב taint דלוק, כל ערך שהגיע מחוץ לתוכנית מסומן כ-tainted, וסימון זה מתפשט דרך כל ביטוי שהערך נוגע בו. המקורות ה-tainted הם:
ארגומנטים של שורת הפקודה (
@ARGV) והסביבה (%ENV);כל קלט מקובץ;
התוצאות של
readdirו-readlink, המשתנה הנקרא על ידיshmread, ההודעות מ-msgrcv, ושדות הסיסמה, ה-gcos וה-shell המוחזרים על ידי קריאותgetpw*;נתוני locale (ראה
perllocale).
אין להשתמש בנתונים tainted, במישרין או בעקיפין, בשום פעולה שמפעילה תת-shell או שמשנה קובץ, ספרייה או תהליך. שימוש בהם בפעולה כזו הוא קטלני, עם הודעה כמו Insecure dependency in %s או Insecure $ENV{PATH}. ישנם שלושה חריגים:
ארגומנטים ל-
printול-syswriteאינם נבדקים;קריאות מתודה סימבוליות (
$obj->$method(@args)) והפניות סימבוליות לשגרות ($foo->(@args)) אינן נבדקות, ולכן אמת את השמות הללו בעצמך אם הם יכולים להגיע מבחוץ;מפתחות hash לעולם אינם tainted.
מטעמי יעילות, taint נעקב באופן שמרני: אם חלק כלשהו של ביטוי הוא tainted, הביטוי כולו מטופל כ-tainted, אפילו במקום שבו הערך ה-tainted אינו משפיע למעשה על התוצאה. taint נצמד לערכים סקלריים בודדים, ולכן חלק מהאיברים של מערך או hash יכולים להיות tainted בעוד אחרים לא.
my $arg = shift; # tainted (from @ARGV)
my $hid = $arg . 'bar'; # also tainted
my $line = <STDIN>; # tainted (file input)
my $data = 'abc'; # not tainted
system "echo $arg"; # dies: Insecure dependency
system "/bin/echo", $arg; # dies: tainted argument
system "echo $data"; # dies until PATH is cleaned (see below)
החריג המבני היחיד ל-tainting של ביטוי שלם הוא האופרטור התלת-מקומי ?:. משום ש
my $result = $tainted ? "untainted" : "also untainted";
הוא רק בחירה מותנית בין שני קבועים, $result אינו tainted אף על פי שהתנאי כן.
כיבוס וזיהוי של נתונים tainted#
הדרך היחידה לנקות את ה-taint מערך היא לחלץ ממנו תת-מחרוזת באמצעות לכידה של ביטוי רגולרי. PetaPerl מניחה שאם טרחת לכתוב תבנית ולשלוף את $1, חשבת על מה שאתה מוכן לקבל. בטל את ה-taint על ידי אימות מול רשימה לבנה של תווים קבילים, לעולם לא על ידי ניסיון להסיר את הרעים:
if ($data =~ /^([-\@\w.]+)$/) {
$data = $1; # $data is now untainted
} else {
die "rejected suspicious input";
}
זה בטוח באופן סביר משום ש-\w, נקודה, מקף וסימן at אינם נושאים משמעות מיוחדת ל-shell. תבנית כמו /(.+)/ הייתה ”מכבסת“ את הערך תוך קבלת כל דבר, מה שמסכל את המטרה. היה מכוון לגבי התבנית.
ישנה הסתייגות הנוגעת ל-locale. תחת use locale קבוצת התווים ש-\w מתאים אליהם נשאבת בעצמה מה-locale, שהוא נתון חיצוני ש-PetaPerl אינה מהימנה עליו, ולכן התאמת \w תחת use locale אינה מבטלת taint. אם עליך לכבס עם \w בתוכנית מודעת-locale, מקם no locale לפני ההתאמה באותו הבלוק.
To test whether a value is tainted, use tainted from the Scalar::Util module. Under -T it correctly reports the taint state of its argument.
ניקוי של %ENV ושל $ENV{PATH}#
PATH שהוא tainted הוא ההפתעה הנפוצה ביותר. משום ש-PetaPerl אינה יכולה לדעת שתוכנית שהיא מריצה לא תיוועץ בעצמה ב-PATH, היא מסרבת להתחיל תת-תהליך כל עוד $ENV{PATH} הוא tainted, עם Insecure $ENV{PATH}. כל ספרייה בנתיב חייבת להיות מוחלטת וניתנת לכתיבה רק על ידי בעליה וקבוצתה; ספרייה ניתנת לכתיבה מניבה Insecure directory in %s. שים לב שרכיב נתיב ריק מטופל כ-. (הספרייה הנוכחית) וגם הוא מפעיל את ההודעה.
PATH אינו האשם היחיד. משום ש-shell עשוי להיוועץ ב-IFS, CDPATH, ENV ו-BASH_ENV, PetaPerl דורשת שאלה יהיו ריקים או לא-tainted לפני שתתחיל תת-תהליך, ומדווחת Insecure $ENV{%s} אחרת. הגדר PATH ידוע כתקין ומחק את משתני ה-shell מוקדם:
$ENV{PATH} = '/bin:/usr/bin';
delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
כאשר עליך לפתוח קובץ או צינור מתוכנית מורשית (setuid או setgid), הדפוס הבטוח הוא לבצע fork לתהליך-בן, לגרום לתהליך-הבן לזנוח את הרשאותיו בחזרה למשתמש ולקבוצה הממשיים, ולתת לתהליך-הבן הבלתי-מורשה לבצע את העבודה ולהעביר את התוצאה בחזרה דרך צינור:
use English;
die "can't fork: $!" unless defined(my $pid = open(my $kid, "-|"));
if ($pid) { # parent
while (<$kid>) {
# consume the child's output
}
close $kid;
} else { # child
my @priv = ($EUID, $EGID);
my $real_uid = $UID;
my $real_gid = $GID;
$EUID = $UID;
$EGID = $GID;
$UID = $real_uid; # drop privileges
$GID = $real_gid;
($EUID, $EGID) = @priv; # make sure they are gone
die "can't drop privileges"
unless $UID == $EUID && $GID == $EGID;
$ENV{PATH} = '/bin:/usr/bin';
exec 'myprog', 'arg1', 'arg2'
or die "can't exec myprog: $!";
}
שים לב ש-exec נקרא עם רשימה, לעולם לא עם מחרוזת בודדת, ולכן אין shell מעורב כדי להרחיב מחדש את הארגומנטים. זוהי הדרך הבטוחה ביותר להפעיל פקודה חיצונית. אותה אסטרטגיית fork-and-drop עובדת עבור glob ו-backticks, אשר (בניגוד ל-system ו-exec) אינם מציעים מוסכמת קריאה בצורת רשימה משלהם.
מצב taint ו-@INC#
תחת -T, PetaPerl מתעלמת ממשתני הסביבה PERL5LIB, PERLLIB ו-PERL_USE_UNSAFE_INC. מתעלמים מהם משום שקל להגדירם מבלי שמשתמש ישים לב, מה שהיה מאפשר לסביבה להזריק ספריות ספרייה בחשאי. עדיין תוכל להתאים את נתיב חיפוש המודולים באמצעות המתג הגלוי -I או הפרגמה lib (-Mlib=/path), משום שאלה מופיעים בבירור בשורת הפקודה.
הספרייה הנוכחית . אינה ב-@INC של ברירת המחדל, בין אם מצב taint דלוק ובין אם לא. (PERL_USE_UNSAFE_INC=1 היה מוסיף אותה מחדש, אך משתנה זה עצמו נמצא בהתעלמות תחת -T, ולכן . נשאר מחוץ ל-@INC עבור כל תוכנית tainted.)
סקריפטים של set-id ומרוץ ה-shebang#
PetaPerl מזהה את תנאי ה-setuid/setgid (מזהה משתמש או קבוצה ממשי השונה מהאפקטיבי) ומפעילה את מצב taint בהתאם; הניסוח ”while running setuid“ / ”while running setgid“ בהודעות חוסר-האבטחה נובע מהזיהוי הזה. PetaPerl אינה, עם זאת, מספקת התקנת pperl של setuid או כל מסייע set-id. אם דרושה לך תוכנית פרל מורשית, הגישה הנתמכת ב-Linux היא עוטף setuid ייעודי קטן שמחטא את הסביבה ואז מבצע exec למפרש על נתיב סקריפט קבוע ובלתי-ניתן-לכתיבה, במקום להסתמך על סקריפט setuid ישירות. מרוץ ה-shebang הקלאסי, שבו הקרנל והמפרש פותחים את קובץ הסקריפט בשני רגעים שונים ותוקף מחליף את הקובץ בינתיים, הוא תכונה של טיפול הקרנל בסקריפטי set-id, לא של המפרש, וגישת העוטף עוקפת אותו.
התקפות סיבוכיות אלגוריתמית#
ניתן לדחוף אלגוריתמים פנימיים מסוימים לצרוך זמן או זיכרון לא-פרופורציונליים באמצעות קלט שנבחר בקפידה, וזהו וקטור מניעת-שירות הבלתי-תלוי במצב taint. מחלקות האיום:
Hash collisions. An attacker who can predict the hash seed can craft keys that all land in one bucket, degrading hash operations. pperl randomises the hash seed at process start and randomises the visible order of
keys,values, andeachper hash. Two environment variables override these defaults for debugging:PERL_HASH_SEEDpins the seed (which controls storage, not the presented order) andPERL_PERTURB_KEYScontrols traversal-order perturbation; theHash::Utilfunctionhash_traversal_maskdoes the same per hash. These overrides weaken the protection and are not advised in production. PetaPerl has never guaranteed any hash key ordering; do not rely on it, and do not use the pseudo-random ordering as a shuffle.מעקב-לאחור קטסטרופלי של regex. מנוע הביטויים הרגולריים הוא NFA ויכול לקחת זמן או מקום שגדל בתלילות כאשר תבנית יכולה להתאים לקלט בדרכים רבות. קלט עוין המותאם מול תבנית פגיעה יכול למצות זיכרון או CPU. תחום את גודל הקלט והעדף תבניות שאינן יכולות לבצע מעקב-לאחור באופן פתולוגי.
מיון.
sortמשתמש ב-mergesort, שהמקרה הגרוע שלו חסום, ולכן לא ניתן לדחוף אותו להתנהגות ריבועית באמצעות קלט עוין.
hashים מסוג tied עשויים לשאת סדר משלהם ומאפייני סיבוכיות משלהם.
הבדלים מן ה-upstream#
Linux בלבד. PetaPerl רצה רק על Linux. הדיון בנושאי set-id ואבטחה של VMS, Windows ופלטפורמות אחרות שפרל המקורית נושאת אין לו מקבילה כאן והוא פשוט נעדר.
אין כלי ”הגנה“. ל-PetaPerl אין מהדר bytecode ואין מנגנון ערפול של מסנני-מקור, ולכן העצה המקורית בדבר אספקת מקור מהודר או מסונן אינה חלה. ערפול מקור מעולם לא היה אמצעי אבטחה בכל מקרה.
תא ה-
Safeעדיין אינו זמין. המודולSafe(והמודולOpcodeשעליו הוא נשען) עדיין אינו זמין ב-PetaPerl; טעינתו כיום נכשלת עםdynamic loading not available in this perl. עד שיהיה, אל תסתמך על הגישה ”הרץ קוד בלתי-מהימן בתא מוגבל“: התייחס לקוד בלתי-מהימן (להבדיל מנתונים בלתי-מהימנים) כאל משהו שיש להריץ ב-sandbox נפרד ברמת מערכת ההפעלה או בתהליך עם הרשאות זנוחות, לא בתוך המפרש.
ראו גם#
perlvar-${^TAINT},%ENV, ומשתני המזהה הממשי/האפקטיבי$<$>$($)שמכריעים אם מצב taint נדלק אוטומטית.perllocale- מדוע נתוני locale אינם מהימנים וכיצדuse localeמתקשר עם כיבוס\w.Command-line switches - המתגים
-T,-tו--Iבהקשר.Command-line options - כיצד pperl מפרשת את מתגיה, כולל בורר זמן הריצה.
Scalar::Util-tainted, לבחינת מצב ה-taint של ערך.Hash::Util-hash_traversal_mask, לשליטה ברמת ה-hash בהגרלת סדר-המעבר.