kind: Secret
apiVersion: v1
metadata:
name: noobaaimages.azurecr.io
labels:
app: noobaa
type: kubernetes.io/dockerconfigjson
data:
.dockerconfigjson: eyJhdXRocyI6eyJub29iYWFpbWFnZXMuYXp1cmVjci5pbyI6eyJ1c2VybmFtZSI6ImJkOTFiYjVjLTE4MTUtNGE4OC1iOWY3LTk1NWY1MWI1YTE0MyIsInBhc3N3b3JkIjoiMDhlOTJkZTAtZTk3YS00NjE4LWE1NTgtNDQ4YzA0MzlkMjk4IiwiZW1haWwiOiJlcmFuLnRhbWlyQG5vb2JhYS5jb20iLCJhdXRoIjoiWW1RNU1XSmlOV010TVRneE5TMDBZVGc0TFdJNVpqY3RPVFUxWmpVeFlqVmhNVFF6T2pBNFpUa3laR1V3TFdVNU4yRXRORFl4T0MxaE5UVTRMVFEwT0dNd05ETTVaREk1T0E9PSJ9fX0=
Next, since it's trivial to unpack that:
$ echo -n "eyJhdXRocyI6eyJub29iYWFpbWFnZXMuYXp1cmVjci5pbyI6eyJ1c2VybmFtZSI6ImJkOTFiYjVjLTE4MTUtNGE4OC1iOWY3LTk1NWY1MWI1YTE0MyIsInBhc3N3b3JkIjoiMDhlOTJkZTAtZTk3YS00NjE4LWE1NTgtNDQ4YzA0MzlkMjk4IiwiZW1haWwiOiJlcmFuLnRhbWlyQG5vb2JhYS5jb20iLCJhdXRoIjoiWW1RNU1XSmlOV010TVRneE5TMDBZVGc0TFdJNVpqY3RPVFUxWmpVeFlqVmhNVFF6T2pBNFpUa3laR1V3TFdVNU4yRXRORFl4T0MxaE5UVTRMVFEwT0dNd05ETTVaREk1T0E9PSJ9fX0=" | base64 -d | jq .
{
"auths": {
"noobaaimages.azurecr.io": {
"username": "bd91bb5c-1815-4a88-b9f7-955f51b5a143",
"password": "08e92de0-e97a-4618-a558-448c0439d298",
"email": "eran.tamir@noobaa.com",
"auth": "YmQ5MWJiNWMtMTgxNS00YTg4LWI5ZjctOTU1ZjUxYjVhMTQzOjA4ZTkyZGUwLWU5N2EtNDYxOC1hNTU4LTQ0OGMwNDM5ZDI5OA=="
}
}
}
From there:
$ docker login noobaaimages.azurecr.io
Username: bd91bb5c-1815-4a88-b9f7-955f51b5a143
Password:
WARNING! Your password will be stored unencrypted in /home/bharrington/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store
Login Succeeded
Now... this is ultimately assuming we had the source code. That being said, it din't really matter in the end.
When it comes to binary analysis, we need to keep in mind our standard toolset(s):
file
strings
gdb
binwalk
floss
We use each of these for it's own purpose. file
and strings
are mechanisms for quickly identifying what a file is and seeing what goodies it might contain. If it's a binary (and since I'm on a Linux machine i'll say "screw it" and limit this to ELF binaries and not PE, aka Windows) we can use gdb
(aka the Gnu Debugger) to run the program and get some more info. On the other hand, if it happens to be some other type of file (firmware for example) we can use binwalk
to dig through it.
When using file
and strings
this should ideally be done in a container. There have been documented vulnerabilities in the Linux binutils
package in the past, so just play it safe.
To start:
$ file noobaa-operator
noobaa-operator: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, Go BuildID=3gULsYdEFl2hnQtEo0Zf/MMCpf2QhFXu_o4EQxF4z/xIHYZB0Sp6B-9PXNIPx7/wfhEYSyXZ7Gj7X4BDbn-, not stripped
So what do we know? It's an ELF binary, compiled for a 64-Bit Linux standard build system. It's statically linked, written in Go, and none of the debugging symbols have been stripped. Woohoo! this just got easier (and harder, but that's just because Go debugging is different from vanilla C).
So now we know that when we run strings
on that binary we will get a bunch of info out of the symbol table, too.
$ strings noobaa-operator
For what it's worth, I'm not dumping that output in here because even just using grep
to narrow that down to lines containing noobaa-operator
there is over 50k lines.
But.... we know know that there is the potential for even more juicy tidbits... So let's dig further.
Floss is a tool for extracting obfuscated strings from binaries. It's useful for digging out the endpoints malware is attempting to talk to, etc.
Personally, I like searching inside of Go binaries intended to talk to Kubernetes to hunt for secrets. So let's zoom out for a second and ask some questions:
- What format are Kubernetes Secrets stored in?
base64
- What character do many base64 strings end in?
=
Why do they end in that? Because of padding. Fortunately for us, this provides a "crib".
- What character set do we need to hunt for base64 strings?
A-Z a-z 0-9 + /
Note: As we mentioned.... the =
is used for padding
Let's glue floss
with a good tool for hunting for strings, grep
:
$ floss noobaa-operator | grep -E '[[:alnum:]/+]{80,}='
apisaposappsargsasn1aumlavx2basebetabindbitsbmi1bmi2bodyboolbullbytecallcap
cas1cas2cas3cas4cas5cas6casecentchancidrcirccodecommcongcopydArrdarrdatadatedeaddef=denydialdropelseemspenspenumermsetageumleuroexecfailfileflagflowfnoffromftpsfuncgotogziphArrhardharrheadhosthourhtmlhttpicmpidleigmpimaginfoint8intsiotaisiniumljobsjsonkey:kindlArrlanglarrleftlinkmacrmakemap[md5
a: %vacircacuteaeligaliasallOfallowalphaamd64argp=aringarrayasympb: %vbad
nbasicbatchbdquoblockboolsbreakburstbytescasp1casp2casp3cdatacedilcellschdirchmodchowncloseclubsconstcountcrarrdebugdeferdeltadepthdiamsecircemailemptyenum=equiverroreventexactexistextrafalsefatalfaultfcntlfieldfilesfloatforcefoundfraslfunc(gammagcinggetwdgob:
groupgzip;hostshttpsicirciexclimageimap2imap3imapsindexinfinint16int32int64ipNetiscsiitemsjson=kappakindslabellaquolceilldquolevellimitline
linuxlocallsquolstatmatchmdashmicrominusmkdirmonthmultinablaname=namesndashnodesnotinocircoeligolineomegaopen
oplusopts:panicparsepatchpathsphasepipe2pipespodIPpop3sportspoundprimeprintproc2protoproxyqueryradicrangeraquorceilrdquoreadyrightrolesroutersquorulesrune
sbquoscalescopesetupsigmasleepslicesocksspec:sse41sse42ssse3statestdinsvqxXszligtest.text/thetathorntildetimestitletls:
tokentradetransucircuint8uintsunionupsihutf-8valueverbswatchwritewwids (MB)
DEPRECATED: GitRepo is deprecated. To provision a container with a git repo,
mount an EmptyDir into an InitContainer that clones the repo using git, then
mount the EmptyDir into the Pod's
container.eyJhdXRocyI6eyJub29iYWFpbWFnZXMuYXp1cmVjci5pbyI6eyJ1c2VybmFtZSI6ImJkOTFiYjVjLTE4MTUtNGE4OC1iOWY3LTk1NWY1MWI1YTE0MyIsInBhc3N3b3JkIjoiMDhlOTJkZTAtZTk3YS00NjE4LWE1NTgtNDQ4YzA0MzlkMjk4IiwiZW1haWwiOiJlcmFuLnRhbWlyQG5vb2JhYS5jb20iLCJhdXRoIjoiWW1RNU1XSmlOV010TVRneE5TMDBZVGc0TFdJNVpqY3RPVFUxWmpVeFlqVmhNVFF6T2pBNFpUa3laR1V3TFdVNU4yRXRORFl4T0MxaE5UVTRMVFEwT0dNd05ETTVaREk1T0E9PSJ9fX0=List
of ports which should be made accessible on the pods selected for this rule.
Each item in this list is combined using a logical OR. If this field is empty or
missing, this rule matches all ports (traffic not restricted by port). If this
field is present and contains at least one item, then this rule allows traffic
only if the traffic matches at least one port in the list.Endpoints is a
collection of endpoints that implement the actual service. Example:
If markdown didn't hate my freedom, I could assure you that each of the 4 things which matched that regex (strings containing the base64 character set of at least 80 lines) pop back in bright red. A very gratifying color.
Let's check them out one by one:
cas1cas2cas3cas4cas5cas6casecentchancidrcirccodecommcongcopydArrdarrdatadatedeaddef=
Immediately i'm pretty certain this is a red herring. Repeating strings and the words "code", "comm", "copy", etc make me dubious, still when we decode it:
q�5q�6q�7q�8q�9q�:q�q��r�r'kr*�r�^r��r��r�rt
�u��u�Zu�^u�u�
Yup. Junk.
nbasicbatchbdquoblockboolsbreakburstbytescasp1casp2casp3cdatacedilcellschdirchmodchowncloseclubsconstcountcrarrdebugdeferdeltadepthdiamsecircemailemptyenum=
Also not expecting much on this one. "basic", "break", "burst", "bytes", et al.
It's probably just coincidence that it ended with an =
. Decode it to get:
�����ڵ�[v��nZ��(���y��-o+^�Ƭ�W����j�wq֭iǝ�W�[�ثr�u�h�w%�ǜ���r���.��+j��y��u�ޭץ��^��]���yȫq隊W��ܞ��
Affirmed. Maybe they're embedding some binary key material... unlikely though with all of those english words.
ostshttpsicirciexclimageimap2imap3imapsindexinfinint16int32int64ipNetiscsiitemsjson=
Yawn. image
, imap
, index
, int16
, items
, json
... Likely these are
defining functions. This is a statically compiled binary so that kindof stuff
is going to happen somewhere... Decode it and we get:
��l��i�'"�Ȟ��b���f��)��x�j�"�ױ�w�)�ר��}����^�+�(�zk#��
eyJhdXRocyI6eyJub29iYWFpbWFnZXMuYXp1cmVjci5pbyI6eyJ1c2VybmFtZSI6ImJkOTFiYjVjLTE4MTUtNGE4OC1iOWY3LTk1NWY1MWI1YTE0MyIsInBhc3N3b3JkIjoiMDhlOTJkZTAtZTk3YS00NjE4LWE1NTgtNDQ4YzA0MzlkMjk4IiwiZW1haWwiOiJlcmFuLnRhbWlyQG5vb2JhYS5jb20iLCJhdXRoIjoiWW1RNU1XSmlOV010TVRneE5TMDBZVGc0TFdJNVpqY3RPVFUxWmpVeFlqVmhNVFF6T2pBNFpUa3laR1V3TFdVNU4yRXRORFl4T0MxaE5UVTRMVFEwT0dNd05ETTVaREk1T0E9PSJ9fX0=
Wink wink. Nudge Nudge. Look familiar? And the survey says:
{"auths":{"noobaaimages.azurecr.io":{"username":"bd91bb5c-1815-4a88-b9f7-955f51b5a143","password":"08e92de0-e97a-4618-a558-448c0439d298","email":"eran.tamir@noobaa.com","auth":"YmQ5MWJiNWMtMTgxNS00YTg4LWI5ZjctOTU1ZjUxYjVhMTQzOjA4ZTkyZGUwLWU5N2EtNDYxOC1hNTU4LTQ0OGMwNDM5ZDI5OA=="}}}
Tada.
The secrets were also compiled into the binaries in a few locations. The best way to dig secrets out of a binary is, of course, a dissassembler and a little knowledge of Intel x86 assembly.
I'm not going to dig into this one. My former tool of choice "back in the day" was ollydbg. Then, when I became a FL/OSS (free, libre, open source software) user I was shit out of luck. This meant running it on a VM.
Later, circa 2009, a rad hex editor underwent a transformation into free mashup between ollydbg and Ida Pro, called Radare2. Now, despite the irony behind it the gold standard (in my opinion) of disassemblers is Ghidra... written by the United States National Security Agency.
I'll summarize what Ghidra can do with a screenshot:
Basically, I loaded the binary and started searching for "secret". Pretty
quickly the string "ImagePullSecret" came back. From there I went to the
function definition, poked around and then found a spot where it was called and
loaded into memory (the LEA
assembly call to "load effective address" from the
64 bit register RAX
).