<?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[Rokkhe Security Research]]></title><description><![CDATA[Rokkhe Security Research]]></description><link>https://rokkhe.hashnode.dev</link><image><url>https://cdn.hashnode.com/uploads/logos/6af6e76a469ebdcd17268531/40aa99b1-b224-4433-805d-ce4eb8f7ab49.png</url><title>Rokkhe Security Research</title><link>https://rokkhe.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Sat, 14 Nov 2026 18:39:07 GMT</lastBuildDate><atom:link href="https://rokkhe.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Test and fix the OWASP Top 10 2025 in your web app this week]]></title><description><![CDATA[Walk your own web app against the OWASP Top 10 2025 with authorized tests, header checks, parameterized queries and a one-week checklist.]]></description><link>https://rokkhe.hashnode.dev/test-fix-owasp-top-10-2025-web-app</link><guid isPermaLink="true">https://rokkhe.hashnode.dev/test-fix-owasp-top-10-2025-web-app</guid><category><![CDATA[owasp]]></category><category><![CDATA[appsec]]></category><category><![CDATA[Web Security]]></category><category><![CDATA[Security]]></category><category><![CDATA[Testing]]></category><dc:creator><![CDATA[Rokkhe]]></dc:creator><pubDate>Sat, 14 Nov 2026 12:30:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6af6e76a469ebdcd17268531/92bcf816-47d4-4f09-ba81-5f1daf5d27e0.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>A practical testing checklist for the current OWASP Top 10, run only against applications you own or are authorized to test. 14 November 2026, Asia/Dhaka. By the Rokkhe research team.</em></p>
<p>The OWASP Top 10 is still the list most teams are asked to “cover.” The current released version is <strong>OWASP Top 10:2025</strong>. If your checklist still starts with the 2021 names, you will miss a new category (mishandling of exceptional conditions), a renamed logging item, and the fact that SSRF now sits under broken access control.</p>
<p>This post is a one-week, authorized testing pass of that 2025 list. It is not a penetration-test report, and it is not a licence to test anyone else’s system. Use two accounts you control, on a staging environment, with written permission if that environment is not yours alone.</p>
<h2>TL;DR</h2>
<ul>
<li>The official 2025 list is published at <a href="https://owasp.org/Top10/2025/">owasp.org/Top10/2025</a>. Start there, not with a 2021 slide deck.</li>
<li>Spend the first day on <strong>A01 Broken Access Control</strong>, including SSRF. That is still where most web apps fail in practice, and it is still first on the list.</li>
<li>The rest of the week covers configuration, cryptography, injection, design, authentication, integrity, logging and how the app fails.</li>
<li>Fix with the usual, boring controls: server-side authorization, parameterized queries, TLS, MFA, and alerts someone actually reads.</li>
<li>Do not run exploit payloads against systems you do not own.</li>
</ul>
<h2>What changed in the 2025 list</h2>
<p>OWASP’s 2025 list, in official order, is:</p>
<ol>
<li>A01:2025 Broken Access Control</li>
<li>A02:2025 Security Misconfiguration</li>
<li>A03:2025 Software Supply Chain Failures</li>
<li>A04:2025 Cryptographic Failures</li>
<li>A05:2025 Injection</li>
<li>A06:2025 Insecure Design</li>
<li>A07:2025 Authentication Failures</li>
<li>A08:2025 Software or Data Integrity Failures</li>
<li>A09:2025 Security Logging and Alerting Failures</li>
<li>A10:2025 Mishandling of Exceptional Conditions</li>
</ol>
<p>OWASP’s own project page and developer guide describe the headline shifts from 2021: SSRF is folded into A01; “vulnerable and outdated components” is broadened into software supply chain failures; and A10 is new, covering applications that fail open, leak data in errors, or leave a transaction half-applied. A09 is renamed to stress that logs only help if they raise an alert someone acts on. Security misconfiguration moves up to second.</p>
<p>You do not need to memorise the politics of the ranking. You need a pass you can finish.</p>
<h2>Start with access control (A01), including SSRF</h2>
<p>Create two ordinary users (Alice and Bob) and one admin user, all in staging. Then, with Alice’s session, try to:</p>
<ul>
<li>Open Bob’s object by changing an id in the URL or in a JSON body (<code>/orders/123</code> → <code>/orders/124</code> when 124 is Bob’s).</li>
<li>Open an admin page or admin API (<code>/admin</code>, <code>/api/admin/users</code>) with Alice’s cookie or bearer token, not the admin’s.</li>
<li>Call a “change role” or “change owner” endpoint as Alice.</li>
<li>After logout, replay Alice’s cookie or token and see whether it still works.</li>
</ul>
<p>A clean result is a consistent <strong>403 or 404</strong> from the <strong>server</strong>, not a hidden button in the browser. If the API returns Bob’s order to Alice, that is a broken access-control finding. Fix it in the service layer: every handler that loads a record by id must check that the caller is allowed to see or change that record. Do not rely on the client to hide the route.</p>
<p>SSRF is in this category in 2025. If your app fetches a URL that a user can influence (a webhook, a preview, an import, a “fetch metadata” field), point it at an address you control on the public internet and confirm the app cannot be steered at internal addresses such as the instance metadata service or <code>localhost</code>. The fix is an allowlist of schemes and hosts, plus a block on private and link-local IP ranges, applied after DNS resolution so a hostname cannot bounce you back to an internal address.</p>
<pre><code class="language-bash"># Read response headers on a staging URL you own.
# Replace the URL. This only sends a GET.
curl -sI 'https://staging.example.invalid/account'

# Look for cookies without Secure or HttpOnly, and for missing
# security headers you have already decided to set.
curl -sI 'https://staging.example.invalid/account' | tr -d '\r' | grep -iE '^(set-cookie|strict-transport-security|content-security-policy|x-frame-options|x-content-type-options):'
</code></pre>
<p>If a session cookie is missing <code>Secure</code> or <code>HttpOnly</code>, fix the cookie flags before you argue about more exotic controls.</p>
<h2>Check configuration, crypto and injection (A02, A04, A05)</h2>
<p><strong>A02 Security Misconfiguration</strong> is the default-on, default-password, debug-left-enabled category. On staging, confirm:</p>
<ul>
<li>Default accounts are disabled or their passwords are no longer the documented default.</li>
<li>Debug or profiler endpoints are not reachable from the address you expose.</li>
<li>Directory listing is off, and error pages do not include stack traces for anonymous users.</li>
<li>TLS is required; HTTP redirects to HTTPS.</li>
</ul>
<p><strong>A04 Cryptographic Failures</strong> is about data at rest and in transit. Confirm the site actually negotiates a current TLS configuration, cookies that hold session material are <code>Secure</code>, and passwords are stored with a purpose-built password hash (see OWASP’s <a href="https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html">Password Storage Cheat Sheet</a>), not a single SHA-256 of the password. Do not invent a home-grown cipher “for performance.”</p>
<p><strong>A05 Injection</strong> is still the failure to keep data and code separate. The control is the same as it has been for years: parameterized queries, not string formatting, for SQL and for other interpreters.</p>
<pre><code class="language-python"># Safe pattern: the SQL stays constant, the value is a bound parameter.
# This is an illustration, not an application.
cur.execute(
    "SELECT id, email FROM users WHERE email = %s",
    (email_from_request,),
)
</code></pre>
<p>When you test, use only your staging database and inputs that belong to your test accounts. If you want to confirm a query is parameterized, read the code or the ORM log. You do not need a destructive payload to prove the control is missing; a code review of the query builder is enough, and it is safer.</p>
<p>Command injection and template injection follow the same rule: do not send untrusted input to a shell, and do not evaluate untrusted text as a template. Prefer library APIs and an allowlist of argument values.</p>
<h2>Design, authentication and integrity (A06, A07, A08)</h2>
<p><strong>A06 Insecure Design</strong> is not a single test. It is the absence of a threat model for a risky feature: password reset, file upload, payment, or a new admin tool. For this week, pick the one feature that can move money or change identity. Write down the actors, what they should be allowed to do, and what happens when a step fails. If nobody can produce that paragraph, the finding is the missing design work, not a clever exploit.</p>
<p><strong>A07 Authentication Failures</strong> (renamed from the 2021 “identification and authentication” title) is easier to test:</p>
<ul>
<li>Can you register with a clearly weak password if the product claims to block them?</li>
<li>Is multi-factor authentication available for privileged users, and can it be skipped on the password-reset path?</li>
<li>Does account recovery leak whether an email is registered, in a way you did not intend?</li>
<li>Are session IDs rotated after login, and invalidated at logout?</li>
</ul>
<p>Use only accounts you created for the test. Do not run a password spray against a real user population.</p>
<p><strong>A08 Software or Data Integrity Failures</strong> covers unsigned updates, CI artefacts you do not verify, and insecure deserialization of untrusted data. For a web app this week: confirm the production image or tarball you deploy is the one the pipeline built (a digest, not a floating <code>latest</code> tag), and that you do not deserialize attacker-controlled bytes into application objects. <a href="https://rokkhe.hashnode.dev/pin-github-actions-sha-lock-workflow-permissions">Post 1 in this series</a> covers a related control in GitHub Actions.</p>
<h2>Logging, alerting and exceptional conditions (A09, A10)</h2>
<p><strong>A09</strong> is no longer only “we have logs.” The 2025 name is Security Logging and <strong>Alerting</strong> Failures. For each of the tests above that should fail (Alice opening Bob’s order, a rejected login), check that a log line exists with a time, a user id, a resource id, and a result, and that something would page a human if the same failure repeated. A log no one reads is not a control.</p>
<p><strong>A10 Mishandling of Exceptional Conditions</strong> is new. It is the app that returns a stack trace, that “fails open” when the authorization service times out, or that credits an account twice when a downstream call errors halfway. For this week:</p>
<ul>
<li>Trigger a 404 and a 500 on staging and read the response body as an unauthenticated user. It should not include stack traces, internal hostnames or connection strings.</li>
<li>If you have a multi-step action (pay, provision, invite), abort it in the middle and check that you cannot be left in an authorized state the design did not intend.</li>
</ul>
<pre><code class="language-bash"># Authorized request against your staging app: look at an error body.
curl -sS -D - 'https://staging.example.invalid/this-route-should-not-exist' -o /tmp/body.txt
head -c 500 /tmp/body.txt
</code></pre>
<p>If the body contains exception names, file paths or SQL, fix the error handler so users get a generic message and the detail stays in a log.</p>
<h2>Supply chain failures (A03) without repeating post 1</h2>
<p>A03 in 2025 is broader than “run <code>npm audit</code> once.” It includes third-party dependencies, build tools, and how updates are delivered. For this week, do the minimum that is still useful:</p>
<ul>
<li>Generate a lockfile (<code>package-lock.json</code>, <code>poetry.lock</code>, <code>go.sum</code>, <code>Cargo.lock</code>) and commit it.</li>
<li>Run the package manager’s audit command in CI and actually assign the findings.</li>
<li>Pin CI Actions by SHA if you have not already (see <a href="https://rokkhe.hashnode.dev/pin-github-actions-sha-lock-workflow-permissions">post 1</a>).</li>
</ul>
<pre><code class="language-bash"># Examples. Run the one that matches the repo, in a clone you own.
npm audit --omit=dev
pip-audit
go list -m all
</code></pre>
<p>Do not treat a raw count of audit findings as a severity ranking. Post 4 in this series is about prioritizing CVEs with KEV, EPSS and CVSS.</p>
<h2>A one-week testing checklist</h2>
<p><strong>Day 1 — A01.</strong> Two-user object tests, admin-route tests, token replay, and a first look at any user-controlled URL fetch.</p>
<p><strong>Day 2 — A02 and A04.</strong> Defaults, debug endpoints, TLS, cookie flags, password storage, data in transit.</p>
<p><strong>Day 3 — A05 and A08.</strong> Parameterized queries, template and command sinks, deploy-artefact integrity.</p>
<p><strong>Day 4 — A07 and A06.</strong> Authentication, reset, session rotation, and a one-page threat note for the riskiest feature.</p>
<p><strong>Day 5 — A09, A10 and A03.</strong> Logs and alerts for the failed tests, error-page review, lockfile and audit command, then a short write-up of what you fixed.</p>
<p>Write findings down as: category, where, how you tested (authorized), what you changed, how you retested. That is enough to be useful next month.</p>
<h2>FAQ</h2>
<p><strong>Is this a full penetration test?</strong>
No. It is a structured self-test of the 2025 Top 10 on an application you are allowed to test. A penetration test can go further, under a written scope.</p>
<p><strong>Should we still use the 2021 list?</strong>
The current released version is 2025. Keep historical reports labelled as 2021 if that is what they used, but new work should follow the 2025 categories.</p>
<p><strong>Where do I put SSRF on a ticket?</strong>
Under A01:2025 Broken Access Control, which is where the 2025 list places it.</p>
<p><strong>Can we automate all of this?</strong>
You can automate header checks, dependency audits and some access-control tests. Authorization still needs cases that only a person who understands the product can invent. Automate the boring checks; do not skip Day 1.</p>
<p><strong>We already have a scanner. Is this redundant?</strong>
A scanner is a useful input. It does not replace the two-user access-control pass or the “does this error fail open?” check.</p>
<h2>Sources</h2>
<ul>
<li>OWASP, <a href="https://owasp.org/Top10/2025/">OWASP Top 10:2025</a></li>
<li>OWASP, <a href="https://owasp.org/www-project-top-ten/">Top 10 project page</a></li>
<li>OWASP Developer Guide, <a href="https://devguide.owasp.org/en/02-foundations/05-top-ten/">OWASP Top 10</a></li>
<li>OWASP Cheat Sheet Series, <a href="https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html">Password Storage Cheat Sheet</a></li>
</ul>
<h2>Next in the series</h2>
<p>Next up: <em>Find leaked secrets in Git and rotate them before they are used.</em></p>
<p>Rokkhe is an AI pentesting and security-operations workspace. Learn more at <a href="https://rokkhe.com/">rokkhe.com</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Pin your GitHub Actions by commit SHA and lock workflow permissions]]></title><description><![CDATA[Pin GitHub Actions to a full commit SHA, lock GITHUB_TOKEN permissions, and avoid pull_request_target pitfalls. A practical hardening checklist.]]></description><link>https://rokkhe.hashnode.dev/pin-github-actions-sha-lock-workflow-permissions</link><guid isPermaLink="true">https://rokkhe.hashnode.dev/pin-github-actions-sha-lock-workflow-permissions</guid><category><![CDATA[GitHub Actions]]></category><category><![CDATA[Security]]></category><category><![CDATA[Devops]]></category><category><![CDATA[ci-cd]]></category><category><![CDATA[supply chain]]></category><dc:creator><![CDATA[Rokkhe]]></dc:creator><pubDate>Sat, 14 Nov 2026 03:22:10 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6af6e76a469ebdcd17268531/e80fc4c9-e891-4737-8425-92fc1f919311.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>A hands-on hardening guide for teams that already ship on GitHub Actions. 14 November 2026, Asia/Dhaka. By the Rokkhe research team.</em></p>
<p>A tag like <code>@v6</code> is convenient, and it is also movable. Anyone who can push a new commit to that tag can change the code your workflow runs the next time the tag is resolved. The same workflow often also hands the job a <code>GITHUB_TOKEN</code> that can write to the repository. Those two defaults — a movable pointer and a write-capable token — are what this post closes.</p>
<p>You will leave with three things: every <code>uses:</code> line pinned to a full 40-character commit SHA, a workflow that starts from read-only permissions, and a short list of <code>pull_request_target</code> mistakes to avoid. All of the commands below are read-first. None of them changes a GitHub setting until you choose to apply the YAML.</p>
<p>This is the first post in a ten-part how-to series. It is the only CI/CD-focused guide in the set. Later posts cover the OWASP Top 10, secrets, CVE triage, cloud and Kubernetes hardening, API and LLM-app security, an AI review harness, and Sigma rules.</p>
<h2>TL;DR</h2>
<ul>
<li>Pin third-party Actions, and your own reusable workflows, to a <strong>full commit SHA</strong>, not a tag or branch. GitHub’s own documentation tells you to do this.</li>
<li>Put a <code>permissions:</code> block at the top of every workflow so the default <code>GITHUB_TOKEN</code> is read-only, then grant extra scopes on the job that needs them.</li>
<li>Treat <code>pull_request_target</code> as a privileged event: it runs in the base repository’s context and can see secrets. Do not check out and run untrusted pull-request code in that workflow.</li>
<li>Audit what you already have before you change production deploy workflows. The commands below only read.</li>
</ul>
<h2>Why a moving tag is not a pin</h2>
<p>An Action is just a Git repository. When a workflow says:</p>
<pre><code class="language-yaml">- uses: actions/checkout@v6
</code></pre>
<p>GitHub resolves <code>v6</code> to whatever commit that tag currently points at. Tags are not immutable. A tag can be deleted and recreated, or force-moved, so the same <code>@v6</code> on Monday is not guaranteed to be the same code on Tuesday.</p>
<p>A full Git commit SHA names one object. This is the form GitHub documents for pinning:</p>
<pre><code class="language-yaml">- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
</code></pre>
<p>The SHA above is an <strong>example of the form</strong>: it is what the <code>v6.1.0</code> tag of <code>actions/checkout</code> resolved to when we checked on 14 November 2026. Before you paste any SHA into a production workflow, resolve it yourself from the Action’s official repository, as shown in the next section. Do not copy SHAs from a blog post and assume they are still the latest release.</p>
<p>Pinning does not mean “never update.” It means updates become a deliberate pull request: a new SHA, a comment with the version, and a review. That is the point. You want the change to be visible.</p>
<h2>Pin every <code>uses:</code> line to a full commit SHA</h2>
<p>Work in a branch. For each Action, resolve the tag you intended to use to a 40-character SHA from the <strong>official</strong> repository, not a fork.</p>
<pre><code class="language-bash"># Example only: resolve a tag on the official actions/checkout repo.
# Replace the URL and tag with the Action you actually use.
git ls-remote https://github.com/actions/checkout.git refs/tags/v6.1.0
</code></pre>
<p>You want the peel to a commit, not an annotated-tag object. If the command prints both a tag object and a peeled commit (<code>^{}</code>), take the commit:</p>
<pre><code class="language-bash">git ls-remote --tags https://github.com/actions/checkout.git 'v6.1.0*'
</code></pre>
<p>Confirm the commit is on the official repo in the browser: <code>https://github.com/OWNER/ACTION/commit/THE_SHA</code>. Then write the pin with the human-readable version in a comment, which GitHub’s documentation encourages so reviewers can see what you meant:</p>
<pre><code class="language-yaml">jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - name: Check out the repository
        uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
</code></pre>
<p>Apply the same rule to reusable workflows:</p>
<pre><code class="language-yaml">jobs:
  call-shared:
    uses: octo-org/common/.github/workflows/build.yml@FULL_40_CHAR_SHA
</code></pre>
<p>A branch name (<code>@main</code>) or a moving major tag (<code>@v2</code>) on a <code>uses:</code> line is not a pin. Abbreviated SHAs are also the wrong form; GitHub asks for the full length.</p>
<p>If you use Dependabot or a similar updater, configure it to open pull requests that bump the SHA and the comment together. The pin is only useful if the update path is boring.</p>
<h2>Set default workflow permissions to read-only, then grant per job</h2>
<p>The <code>GITHUB_TOKEN</code> is created automatically for each job. GitHub’s guidance is to grant it the minimum permissions the job needs, and to set the default for repository contents to read-only. The clean way to do that in the workflow file is an explicit top-level block:</p>
<pre><code class="language-yaml">name: CI

on:
  pull_request:
  push:
    branches: [main]

permissions:
  contents: read

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
      - run: make test
</code></pre>
<p>When a job must write, raise the scope on <strong>that job only</strong>:</p>
<pre><code class="language-yaml">jobs:
  release:
    runs-on: ubuntu-latest
    permissions:
      contents: write
      id-token: write
    steps:
      - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
      # publish steps go here
</code></pre>
<p><code>id-token: write</code> is not a repository-write grant. It only lets the job request an OIDC token for a cloud exchange. That is expected if you later move off long-lived cloud keys, but it should still be limited to the job that talks to the cloud.</p>
<p>Also set the repository (or organization) default in the GitHub UI so a workflow file that forgets <code>permissions:</code> does not silently get a write token: <strong>Settings → Actions → General → Workflow permissions → Read repository contents and packages permissions</strong>. The YAML block is still required, because it is reviewed with the code.</p>
<p>Do not use <code>permissions: write-all</code>. If you see it in a copied example, delete it.</p>
<h2>Treat <code>pull_request_target</code> as a privileged event</h2>
<p><code>pull_request</code> runs in the context of the merge commit and, for a fork, with a restricted token and no secrets. <code>pull_request_target</code> is different. GitHub’s documentation is explicit: the workflow runs in the <strong>base</strong> repository’s context, with the base repository’s <code>GITHUB_TOKEN</code> and access to secrets. That is what makes it useful for labelling or commenting on pull requests from forks. It is also what makes it dangerous.</p>
<p>The safe pattern is: the workflow file, and any <code>actions/checkout</code> that does not pass an explicit <code>ref</code>, come from the default branch of the base repository. Untrusted code from the pull request is not executed.</p>
<p>The unsafe pattern, which GitHub and the GitHub Security Lab have both warned about, is to check out the pull request’s untrusted ref and then run its install or test scripts in that same privileged job:</p>
<pre><code class="language-yaml"># Unsafe pattern — do not use this.
# It is shown only so you can recognise it in a review.
on:
  pull_request_target:

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
        with:
          ref: ${{ github.event.pull_request.head.sha }}
      - run: npm install &amp;&amp; npm test   # this runs untrusted code with base secrets
</code></pre>
<p>If you need to comment on a fork pull request, keep the privileged job small: no checkout of the head SHA, no <code>npm install</code> of the head branch, no running of the contributor’s Makefile. Use a <code>pull_request</code> workflow for tests, and a tightly scoped <code>pull_request_target</code> workflow only for authentication-needed comments, with <code>permissions</code> limited to <code>pull-requests: write</code>.</p>
<p>Two newer guardrails are worth knowing. GitHub now applies a default Actions event policy that blocks <code>pull_request_target</code> in public repositories that have no event policy of their own. It currently runs in evaluate mode, and GitHub says it will enforce it on 2 December 2026, so check your policy insights now if you rely on this event. Recent versions of <code>actions/checkout</code> also refuse to check out a fork's pull-request head in this context unless you set <code>allow-unsafe-pr-checkout: true</code>. Treat that flag as a review red flag: only set it when the checked-out code is read as data and never executed.</p>
<p>GitHub also documents cache behaviour for this event: <code>pull_request_target</code> workflows get read-only access to the default branch’s cache, unless you opt into a writeable cache mode, which reintroduces cache-poisoning risk. Leave the default alone.</p>
<h2>A copy-paste starter workflow</h2>
<p>Use this as a starting point for a private or public repository that runs tests on pull requests. Resolve the checkout SHA yourself before you merge it.</p>
<pre><code class="language-yaml">name: CI

on:
  pull_request:
  push:
    branches: [main]

permissions:
  contents: read

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - name: Check out
        uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0

      - name: Run tests
        run: |
          set -euo pipefail
          if [ -f Makefile ]; then
            make test
          elif [ -f package.json ]; then
            npm ci
            npm test
          else
            echo "Add the test command for this repository."
            exit 1
          fi
</code></pre>
<p>There is no <code>pull_request_target</code> here, no <code>write-all</code>, and no secrets. Add deploy jobs in a separate workflow, on a trusted event such as <code>push</code> to <code>main</code> or a release, with job-level <code>permissions</code> and an environment that requires a reviewer if the job can touch production.</p>
<h2>How to audit the workflows you already have</h2>
<p>These commands only read the files on disk. Run them in a clone of the repository.</p>
<p>List every workflow and every <code>uses:</code> line that is not pinned to a 40-character SHA:</p>
<pre><code class="language-bash"># From the repository root.
find .github/workflows -type f \( -name '*.yml' -o -name '*.yaml' \) -print

# uses: lines that do not end in a 40-character hex SHA (tags, branches, short SHAs).
grep -nE 'uses:' .github/workflows/*.{yml,yaml} 2&gt;/dev/null \
  | grep -vE 'uses:[[:space:]]*[^@]+@[0-9a-f]{40}([[:space:]]|#|$)'
</code></pre>
<p>List workflows that never set <code>permissions:</code>:</p>
<pre><code class="language-bash">for f in .github/workflows/*.{yml,yaml}; do
  [ -f "$f" ] || continue
  if ! grep -qE '^[[:space:]]*permissions:' "$f"; then
    printf 'No permissions block: %s\n' "$f"
  fi
done
</code></pre>
<p>List <code>pull_request_target</code> workflows so a person can review them:</p>
<pre><code class="language-bash">grep -n 'pull_request_target' .github/workflows/*.{yml,yaml} 2&gt;/dev/null
</code></pre>
<p>On GitHub, also open <strong>Actions → workflow run logs</strong> after a test change and confirm secrets are redacted, as GitHub recommends in its secure-use reference. If a secret ever appears in a log, delete that log and rotate the secret. Finding a leak is the subject of post 3 in this series.</p>
<h2>Checklist</h2>
<ul>
<li><input disabled type="checkbox" /> Every <code>uses:</code> line, including reusable workflows, pins a full 40-character SHA from the official repository.</li>
<li><input disabled type="checkbox" /> A version comment sits next to each SHA so reviewers can see the intended release.</li>
<li><input disabled type="checkbox" /> Each workflow file has a top-level <code>permissions:</code> block; the default is <code>contents: read</code>.</li>
<li><input disabled type="checkbox" /> Write scopes are granted only on the job that needs them, never with <code>write-all</code>.</li>
<li><input disabled type="checkbox" /> The repository (or organization) default workflow permission is also read-only.</li>
<li><input disabled type="checkbox" /> <code>pull_request_target</code> is used only when a base-repository token is required, and that job does not check out and execute untrusted head code.</li>
<li><input disabled type="checkbox" /> Deploy jobs use a GitHub Environment with required reviewers when they can reach production.</li>
<li><input disabled type="checkbox" /> An updater (Dependabot or equivalent) can open PRs that bump the SHA and the comment together.</li>
<li><input disabled type="checkbox" /> Someone has grepped the existing workflows with the audit commands above.</li>
<li><input disabled type="checkbox" /> If a secret was ever printed in a log, that log was deleted and the secret was rotated.</li>
</ul>
<h2>FAQ</h2>
<p><strong>Is pinning to</strong> <code>@v6.1.0</code> <strong>(an exact tag) good enough?</strong> No. An exact tag is more stable than <code>@v6</code>, but the tag can still be moved. GitHub’s documented pin is the full commit SHA, with the tag in a comment.</p>
<p><strong>Does a SHA pin mean we are stuck on an old Action?</strong> No. You update by changing the SHA in a pull request, the same way you update any other dependency. The difference is that the update is reviewed, not silent.</p>
<p><strong>Can we pin to a short SHA?</strong> Do not. Use the full 40-character object name.</p>
<p><strong>We need to comment on pull requests from forks. Must we use</strong> <code>pull_request_target</code><strong>?</strong> Often yes, because a privileged token is required to comment. Keep that workflow small, grant only <code>pull-requests: write</code>, and do not run the contributor’s build in that job.</p>
<p><strong>Will this break Dependabot?</strong> Only if Dependabot is not configured to update Actions. Once it is, it should open PRs that retarget the SHA. Review those PRs the way you review application bumps.</p>
<h2>Sources</h2>
<ul>
<li>GitHub Docs, <a href="https://docs.github.com/en/actions/how-tos/write-workflows/choose-what-workflows-do/find-and-customize-actions">Using pre-written building blocks in your workflow</a></li>
<li>GitHub Docs, <a href="https://docs.github.com/en/actions/reference/security/secure-use">Secure use reference</a></li>
<li>GitHub Docs, <a href="https://docs.github.com/en/actions/reference/security/securely-using-pull_request_target">Securely using pull_request_target</a></li>
<li>GitHub Docs, <a href="https://docs.github.com/en/actions/tutorials/authenticate-with-github_token">Use GITHUB_TOKEN for authentication in workflows</a></li>
</ul>
<h2>Next in the series</h2>
<p>Next up: <em>Test and fix the OWASP Top 10 2025 in your web app this week.</em></p>
<p>Rokkhe is an AI pentesting and security-operations workspace. Learn more at <a href="https://rokkhe.com/">rokkhe.com</a>.</p>
]]></content:encoded></item><item><title><![CDATA[CVE-2026-123039: How PHP2Shell Became Root on Check Point Gateways, and How to Close It]]></title><description><![CDATA[Vulnerability briefing · 13 November 2026 · By the Rokkhe research team CVE-2026-123039 is a high-severity (CVSS 7.8) privilege escalation in the…]]></description><link>https://rokkhe.hashnode.dev/cve-2026-123039-how-php2shell-became-root-on-check-point-gateways-and-how-to-close-it</link><guid isPermaLink="true">https://rokkhe.hashnode.dev/cve-2026-123039-how-php2shell-became-root-on-check-point-gateways-and-how-to-close-it</guid><category><![CDATA[Security]]></category><category><![CDATA[cybersecurity]]></category><category><![CDATA[Devops]]></category><category><![CDATA[#infosec]]></category><dc:creator><![CDATA[Rokkhe]]></dc:creator><pubDate>Fri, 13 Nov 2026 10:36:48 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6af6e76a469ebdcd17268531/5361e119-8eb8-4b5f-abf9-820624910e99.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>Vulnerability briefing · 13 November 2026 · By the Rokkhe research team</em></p>
<p><strong>CVE-2026-123039 is a high-severity (CVSS 7.8) privilege escalation in the DynamicCLI service of Check Point Gaia OS. On 12 November 2026, Check Point confirmed that attackers chained it with PHP2Shell in the Mobile Access portal to gain root on unpatched gateways. Install the November Jumbo Hotfix, then hunt for the published indicators.</strong></p>
<h2>TL;DR</h2>
<ul>
<li><strong>What:</strong> a root process on Gaia (<code>dynamic_server</code>) accepts commands from a world-writable local socket without checking who is connecting. Anyone already running code on the box can become root.</li>
<li><strong>How it was used:</strong> PHP2Shell (CVE-2026-109618 / CVE-2026-109619) gave the attacker code execution through the Mobile Access portal. CVE-2026-123039 turned that into root.</li>
<li><strong>Who:</strong> Check Point Force Firewalls, Management and Log Servers, Multi-Domain servers and Maestro Orchestrator on R81 to R82.20. Spark Firewalls and Smart-1 Cloud are not affected by CVE-2026-123039.</li>
<li><strong>Exploited?</strong> Yes. Check Point saw the chain used against "a couple of dozen" unpatched systems between 30 October and 4 November 2026.</li>
<li><strong>Fix:</strong> November Jumbo Hotfix: R82.20 Take 14, R82.10 Take 52, R82 Take 132 or R81.20 Take 174 or later.</li>
<li><strong>Then:</strong> patching closes the door, but it does not remove an attacker who is already inside. Hunt before you call it done.</li>
</ul>
<h2>What is CVE-2026-123039?</h2>
<p>Gaia's dynamic (extended) CLI is served by a root process called <code>dynamic_server</code>. It listens on a local socket at <code>/tmp/dynamic_socket</code>. According to Check Point's advisory (<a href="https://support.checkpoint.com/results/sk/sk1000362">sk1000362</a>), on affected versions that socket is created world-writable (0777), the connecting process is not authenticated, and the daemon runs whatever command string it receives as root.</p>
<p>On its own, this is a local bug: CVSS 3.1 vector <code>AV:L/AC:L/PR:L/UI:N</code>, score 7.8. An attacker first needs to run code on the appliance. That is exactly what PHP2Shell provides.</p>
<h2>Why this one matters most this week</h2>
<p>We weighed everything disclosed or newly exploited from 11 to 13 November, including a critical Apache HTTP Server mod_http2 bug (CVE-2026-119618) and a large Citrix NetScaler patch set. Neither had confirmed in-the-wild exploitation when we checked. This chain does. It hits the security gateway itself, the device that sits at the edge of the network and holds VPN access and the firewall policy. In some of the incidents the attackers also reached a hypervisor.</p>
<h2>Who is affected?</h2>
<p><strong>CVE-2026-123039 (local privilege escalation, <a href="https://support.checkpoint.com/results/sk/sk1000362">sk1000362</a>):</strong></p>
<ul>
<li>Products: Check Point Force Firewalls; Management and Log Servers; Multi-Domain Security Management and Multi-Domain Log Server; Maestro Orchestrator.</li>
<li>Affected versions: R82.20, R82.10, R82, R81.20, plus R81.10 and R81 (both end of support).</li>
<li>Not affected: Check Point Spark Firewalls and Smart-1 Cloud.</li>
</ul>
<p><strong>PHP2Shell on Check Point (remote code execution, <a href="https://support.checkpoint.com/results/sk/sk1000302">sk1000302</a>).</strong> This applies to firewalls that use any of these features:</p>
<ul>
<li>Mobile Access Portal</li>
<li>Remote Access VPN with SSL Network Extender</li>
<li>Remote Access VPN with SAML realm authentication</li>
<li>Identity Awareness Captive Portal</li>
<li>UserCheck Portal</li>
<li>Zero Phishing</li>
</ul>
<p>Affected versions are R81.20 to R82.20, older end-of-support releases from R80, and Spark R81.10.x and R82.00.x. Security Management and Log Servers are not affected by PHP2Shell.</p>
<p><strong>Fixed versions (both issues):</strong></p>
<ul>
<li>R82.20: Jumbo Hotfix Take 14 or later</li>
<li>R82.10: Jumbo Hotfix Take 52 or later</li>
<li>R82: Jumbo Hotfix Take 132 or later</li>
<li>R81.20: Jumbo Hotfix Take 174 or later</li>
<li>Spark R82.00.11: build B998002843 or later</li>
<li>Spark R81.10.17: build B996005005 or later</li>
</ul>
<h2>How the attack works</h2>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/ryg8uu2obndzb0i98i0w.png" alt="Portal to root in four steps" /></p>
<p>This is how Check Point Research described the intrusions, in plain terms:</p>
<ol>
<li><strong>Entry.</strong> The attacker reached an internet-facing Mobile Access portal on a gateway that did not have the 23 October PHP2Shell hotfix.</li>
<li><strong>Code execution.</strong> PHP2Shell, a use-after-free class of flaws in PHP request handling, gave them code execution as the portal's service account. Check Point found PHP session files containing the marker <code>upload_progress_pwn</code>, and hundreds of crashes in the portal's web workers.</li>
<li><strong>Root.</strong> The attacker wrote a command to the DynamicCLI socket (CVE-2026-123039), and it ran as root.</li>
<li><strong>Persistence and pivot.</strong> They planted PHP webshells as <code>zz*.tpl</code> templates in the portal, and a backdoor disguised as <code>CPconfigdaemon</code> that later runs as <code>slot_CPconfigdaemon</code> and drops <code>/lib/libcpfwcommon.so</code>. They scanned the internal network, moved over SSH to a VMware ESXi host using the <code>vpxuser</code> account, and in a limited number of cases ran a wiper.</li>
</ol>
<p>Check Point says a first wave on 30 October focused on organisations in one region (Israel). A second wave on 2 November scanned organisations in several countries, with no evidence of destructive activity. Check Point also says the evidence does not support a high-confidence attribution.</p>
<h2>How to check if you're affected</h2>
<p>Run these on every gateway, cluster member and management server. Use Expert mode unless noted. All of them only read; none changes anything.</p>
<ul>
<li><strong>Version and Jumbo take:</strong> run <code>show version all</code> in clish, or <code>cpinfo -y all</code> in Expert mode and read the <code>JUMBO_HF_MAIN</code> take. Compare it with the fixed takes above.</li>
<li><strong>Exposure to PHP2Shell:</strong> <code>enabled_blades</code> lists the active blades. Look for Mobile Access and Identity Awareness, then check in SmartConsole which portals face the internet.</li>
<li><strong>The vulnerable socket:</strong> <code>ls -l /tmp/dynamic_socket</code>. A world-writable socket (srwxrwxrwx) on an unpatched take matches the advisory's description.</li>
<li><strong>After patching:</strong> <code>grep "Rejected connection from unauthorized uid" /var/log/dynamicCli.log</code>. The fix blocks and logs unauthenticated calls, so hits here are worth a closer look.</li>
</ul>
<h2>How to fix it</h2>
<ol>
<li><strong>Install the November Jumbo Hotfix</strong> on all Management Servers and Security Gateways, starting with gateways that expose Mobile Access. The takes are listed above.</li>
<li><strong>If you cannot patch PHP2Shell today,</strong> apply the <code>cp-httpd.conf</code> mitigation in <a href="https://support.checkpoint.com/results/sk/sk1000302">sk1000302</a> and restart the portals. Check Point also offers a standalone PHP2Shell hotfix, which needs a reboot. R82.20 gateways are protected by the IPS signature "PHP Remote Code Execution", even when IPS is not enabled.</li>
<li><strong>If you cannot patch CVE-2026-123039 today,</strong> follow the <a href="https://support.checkpoint.com/results/sk/sk1000362">sk1000362</a> mitigation, which stops the <code>dynamic_server</code> process and keeps it from restarting. This breaks the Dynamic CLI commands listed in sk144112.</li>
<li><strong>Retest from the outside.</strong> Confirm the portals respond as expected and the take is correct on every cluster member, not just the active one.</li>
</ol>
<h2>Detection and hunting</h2>
<p><img src="https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/lvrler4mtuw5cqg2uapf.png" alt="Check, patch, hunt, retest" /></p>
<p>Check Point notes that no match does not rule out compromise. On the other hand, any match calls for immediate investigation.</p>
<p><strong>On the appliance (read-only):</strong></p>
<ul>
<li><code>grep -rl upload_progress_pwn /opt/CPcvpn-*/tmp/ 2&gt;/dev/null</code> finds PHP session artefacts from the exploit.</li>
<li><code>find / -xdev \( -name 'zz*.tpl' -o -name '.up.tpl' \) 2&gt;/dev/null</code> finds the webshell templates.</li>
<li><code>ls -la /tmp/CPconfigdaemon /tmp/.bd.log /tmp/.p_sock /lib/libcpfwcommon.so 2&gt;/dev/null</code> checks for backdoor files.</li>
<li><code>ps -ef | grep -i '[C]Pconfigdaemon'</code> and <code>netstat -tlnp | grep ':11072'</code> check for the backdoor process and its listener.</li>
<li>Check SHA-256 hashes of anything suspicious against the IOC table in <a href="https://research.checkpoint.com/2026/php2shell-exploitation-on-check-point-gateway/">Check Point Research's report</a>.</li>
</ul>
<p><strong>In your SIEM:</strong></p>
<ul>
<li>Splunk: <code>index=* ("130.94.19.204" OR "130.94.19.168")</code></li>
<li>Microsoft Sentinel: <code>CommonSecurityLog | where SourceIP in ("130.94.19.204","130.94.19.168") or DestinationIP in ("130.94.19.204","130.94.19.168")</code></li>
<li>SSH logins to ESXi hosts as <code>vpxuser</code> that come from a gateway address.</li>
<li>Gaps in firewall traffic logs, especially after the logging daemon crashes.</li>
</ul>
<p>If you find a match, treat the gateway as compromised. Bring in Check Point support and your incident-response process, preserve evidence, and rotate the credentials that pass through or are stored on the gateway.</p>
<h2>Severity, KEV and EPSS</h2>
<ul>
<li><strong>CVE-2026-123039:</strong> CVSS 3.1 7.8 (High), assigned by Check Point as the CNA. NVD analysis is pending. Check Point and the Canadian Centre for Cyber Security (AV26-1154) report it as exploited in the wild. It was not yet in CISA's KEV catalog when we checked on 13 November (catalog version 2026.11.10), and FIRST has not yet published an EPSS score for it.</li>
<li><strong>PHP2Shell (CVE-2026-109618 / CVE-2026-109619):</strong> CVSS 9.2 (Critical). It has been in CISA KEV since 26 October 2026, with a federal due date of 29 October. Its EPSS score on 12 November was 0.99999, the top of the scale.</li>
</ul>
<h2>FAQ</h2>
<p><strong>What is CVE-2026-123039?</strong>
It is a local privilege escalation in Check Point Gaia OS. The root <code>dynamic_server</code> process accepts commands from a world-writable socket without authentication, so anyone already running code on the appliance can become root. CVSS 7.8.</p>
<p><strong>Is CVE-2026-123039 being exploited?</strong>
Yes. Check Point confirmed exploitation, chained with PHP2Shell, against a couple of dozen unpatched systems between 30 October and 4 November 2026.</p>
<p><strong>Which Check Point versions fix it?</strong>
The November Jumbo Hotfix fixes it: R82.20 Take 14, R82.10 Take 52, R82 Take 132 and R81.20 Take 174 or later. R81 and R81.10 are end of support, so plan an upgrade.</p>
<p><strong>My gateway doesn't use Mobile Access. Am I safe?</strong>
You are less exposed, but not safe. PHP2Shell also reaches gateways through SSL Network Extender, SAML VPN, Captive Portal, UserCheck and Zero Phishing. CVE-2026-123039 also affects management servers. Patch everything.</p>
<p><strong>I patched. Am I done?</strong>
Not yet. Patching stops new break-ins. Hunting for the indicators above shows whether someone got in before you patched.</p>
<h2>References</h2>
<ul>
<li><a href="https://blog.checkpoint.com/security/security-advisory-active-exploitation-of-php2shell-and-local-privilege-escalation/">Check Point Blog: Security Advisory: Active Exploitation of PHP2Shell and Local Privilege Escalation (12 Nov 2026)</a></li>
<li><a href="https://research.checkpoint.com/2026/php2shell-exploitation-on-check-point-gateway/">Check Point Research: PHP2Shell Exploitation on Check Point Gateway (12 Nov 2026)</a></li>
<li><a href="https://support.checkpoint.com/results/sk/sk1000362">Check Point sk1000362: CVE-2026-123039</a></li>
<li><a href="https://support.checkpoint.com/results/sk/sk1000302">Check Point sk1000302: PHP2Shell response</a></li>
<li><a href="https://nvd.nist.gov/vuln/detail/CVE-2026-123039">NVD: CVE-2026-123039</a></li>
<li><a href="https://www.cyber.gc.ca/en/alerts-advisories/check-point-security-advisory-av26-1154">Canadian Centre for Cyber Security AV26-1154</a></li>
<li><a href="https://www.cisa.gov/known-exploited-vulnerabilities-catalog">CISA Known Exploited Vulnerabilities catalog</a></li>
<li><a href="https://github.com/php/php-src/security/advisories/GHSA-6wmf-4rv5-p3rp">PHP security advisory GHSA-6wmf-4rv5-p3rp</a></li>
<li><a href="https://api.first.org/data/v1/epss?cve=CVE-2026-109619">FIRST EPSS</a></li>
<li><a href="https://cve.now/CVE-2026-123039">cve.now</a></li>
</ul>
<h2>From "patched" to "proven"</h2>
<p>Attackers struck about a week after the fix shipped, and they chained two bugs: one at the edge, one on the box. Most teams found out about the second one only this week. That gap is the job.</p>
<p>This briefing was written by the Rokkhe research team. Rokkhe is an AI pentesting and security-operations workspace. Learn more at <a href="https://rokkhe.com/">rokkhe.com</a>.</p>
]]></content:encoded></item></channel></rss>