Okay, so we are on time.
Please take a seat if you're not sitting.
And welcome, William.
Thank you.
Hey, everybody.
Thank you for staying so late for my talk.
I know it's pretty late, so I appreciate it.
Today, I want to talk a little bit about it.
A little bit about it.
A couple of months ago, called Zizmour,
and how I've been using it.
It's a hunt for it.
Get a back-ends box, and how you can use it as well.
So just a little bit about myself.
My name is William.
That's my website.
Currently I'm an engineering director at Trail of Bits.
But this is not a work talk.
And these slides, normal my opinions that I express throughout them,
or the opinions of my employer.
And outside of work, I'm also a maintainer of home brew,
as well as a member of PIPA, and contributed to PIPA,
and a few other things.
So you'll see me possibly make my attention to some of those.
And then if you want that QR code has these slides.
And so if you want to follow along, there's some parts
where you can try in real time.
The things I'm going to show you.
So you can try to exploit it in yourself.
But I should just skip that.
So let me do it and try to get a back-ends.
I think I'll probably, actually, a quick show of hands.
How many people have used get a back-ends in this room?
OK, although only majority.
So I'm not going to do too much of a intro.
But there's a conclusion.
It's get up to CIT offering.
It's free for open source.
It costs money, otherwise.
It's a bunch of Yamohel.
Work flows are, sort of, top-level units of execution.
They contain jobs, jobs run on VMs, VMs, contain jobs that
have steps.
And each step is an independent sequence of shell commands
or recomposed actions which contain themselves, other actions
or shell commands.
These can be checked out from other repos.
So actions check out, actually, check out the actions check
out repos and runs code from that repo.
Those actions themselves are also Yamohel.
They define reasonable operations
that you can share between repositories.
The official ones, which are generally under the actions
namespace, as well as third-party ones, any public or private
repo and GitHub, as well as local actions,
as you can call an action that is defined somewhere
in your own repository.
If you have a checked out within your job,
these are full-generally.
They can run code, they can run other actions, et cetera.
And importantly, they run in the context of the job
that executes them, meaning that any sequence credentials, et cetera,
that you give that job the action also has.
And also, sort of, importantly, as we'll see later,
they have file access to the jobs runner state,
which includes the file system.
Which contains all kinds of interesting and useful things,
as well as other processes running on that VM.
So bottom line, GitHub actions are arbitrary
code executions of service.
That will come up in various important ways
throughout the stock.
It's a very powerful system, GitHub actions.
GitHub actions, it plugs into the broader permissions
well that the GitHub offers.
Every workflow runs for the latent secrets GitHub token
or GitHub.token, depending on the context you're in.
And by default, unless you opt into a more secure mode,
this token has a lot of powers,
including modifying the repo contents of the workflow,
of the repo that contains the workflow
that is loaded with it.
If you are a workflow author, you can also up or downscope
this credential, after you work flow job or step level
with permissions box, which tell that scope of action,
how many credentials it has, which you can't exceed
with that default token.
However, there's some wonky behavior there,
where permissions are inherited from parent job
or workflow context, if they're not set,
but if they're explicitly set, they're shadowed.
So they're not telescoping, they're shadowing, so that's.
And again, this comes up in important ways.
So of course, you can do things like on the right
where you have the explicit readwriter
and on permissions for each sort of scope,
or you can set read all or you can do an empty object,
which means, give me no permissions at all.
However, that's so produces a token.
And a token just gives you sort of larger rate limit limits,
versus not having a token.
Another way in which GitHub access is extremely powerful,
which, again, will come up when we talk about the security,
aspects of all of this, is that it has a,
the extremely powerful expression system.
Most parts of the workflow are action definition scheme
in YAML support expressions, which are defined via
this, like, curly syntax, where you put an expression
in the middle, sometimes you don't need those curly,
such as in an escondition.
These are questions that have a lot of functionality
within them.
They can do math, they have control flow,
they can encode and decode JSON and call a set of functions
that are defined within the runner itself, et cetera.
They can also directly, rather, when you expand them,
they expand directly into other context references in them.
So in this condition, then block a withinput,
or even a run body, which contains, obviously, shell scripts
that you're trying to run.
You can expand values directly into the shell scripts.
At the core of the expression system
are these things that get up all's context,
which are really just JSON objects.
When the runner is invoked, it is loaded with a set of context,
and you can access those contexts, sort of,
is if they were, like, JSON script objects.
So if you have nested dictionary,
that has a secret stocked up token
that gets modeled as the context,
secret stocked up token, and the expression syntax.
And these contexts come from both static and dynamic sources.
So the static source is something that you define sort of,
ahead of time, for the runner to load,
so that's the one is configuration,
as well as get inside states, to the event name,
the event payload trigger, the workflow,
and dynamic is symmetric expansions.
I realize I have a way more stuff than I thought,
so I'm going to kind of breeze through this.
Even more powerful, you have these special variables,
you can set environment variables, you can set the path,
you can set variables between steps,
you can set steps on trees.
There's also a special file system state,
like event path and tool cache,
and these are, again, useful and we'll see.
So now let's talk about the security of all of that.
So before I do that, what is it in this matter?
Well, one answer is it's well, the popular, and it does everything.
That's a default choice for GitHub, meaning that,
knowing people use it, pretty much everybody in this room uses it.
As a corollary, it's used by developers with huge skill ranges,
meaning that people who just want to get something done
are using the same way people who are experts in SCD,
deployment, and it has this massive range of uses,
including most interestingly in the me,
people publish the PIPI, et cetera, using GitHub actions,
which means that the security of their package on those indices
directly mirrors the security of the system that publishes that.
So no one's the users, plus really powerful and complex
to the system in security files.
So let's break some of this stuff.
OK, now crowd question, find the vulnerability on us.
Let's see a few.
Anybody know anybody know?
Yep, OK, somebody's mentioned, right?
What we're doing is we're expanding the value of GitHub event
pull requests tied all directly into the shell context,
which of course I can give it.
Hello, cynical and cut it.
See password, and now I'm running an arbitrary code.
So again, the core thing here is, this is something
that the attacker controls.
The title is an attacker tool value that is loaded.
Anybody can create a pull request on a public repository.
If you put your title as, hello, some of you call one,
cut it, see password, you're now running code in the context
of the workflow that triggered it.
That's baby steps.
OK, what about this one?
We've quoted it this time.
Anybody see a problem?
It's the same vulnerability.
It's the same-down vulnerability.
And the reason for this is going back to expressions.
Expressions do not care about shell level voting,
because they're injected by the actions runner
before any shell processing happens.
They have no chance for the shell to reparse,
you've injected.
It's exactly the same thing as SQL injection.
You can code as much as you want in SQL injection,
but if you're directly injecting the value,
you can always inject in a scape, so the escaping.
So in this case, our payload will be hello, quote, payload.
And we get the exact same thing.
So this is baby steps.
OK.
We'll see the vulnerability here.
OK, that's enough time.
So it's nothing about any other steps.
All the steps are somatically correct.
However, what happens is actions checkout.
That's what we call out there.
The first one, it persists silently
to get up token by default.
So when you run actions checkout,
that context token that you use gets stored in.getconfig
in the repo that you check out.
Then, when you run actions, build up load artifact
with the current directory as a path,
you have now persisted your credential to a public artifact.
Anybody who goes down with that artifact now
has your credential.
That credential's only alive for about four hours
to find them correctly, but for four hours,
you've now leaked potentially a global right
credential figure repository.
Still big steps.
They fix this by the way, but they fix it by not patching
and checkout, but by patching or upload artifact
to ignore that files, which broke Python coverage.
OK, getting a little bit more interesting.
And we see the bug in this one.
You'll notice that we are no longer
doing environment expression injection.
We're now forwarding by an environment variable.
So there's no more rejection vulnerability there.
OK.
Like I said, good environment is one of those special variables
to file, but it's just a file, nothing special.
However, we can write multiple lines to it at once.
So in this case, if a user or an attacker
submits a title that matches that expression before,
that expression because it uses the, I believe it's a p flag,
it prints multiple times, once for each match.
So in this case, we've injected two variables
when we meant to only inject one.
We've injected message equals two
and also LD-free load equals hackme.so.
And so now, if you can put right, if you have right access
to a runner, even if you don't have execute access,
you've turned your right access into execute access.
Because LD-free load will run in front of anybody
and nobody, they can execute it.
So there you go.
OK?
Let's get a little bit more advanced.
Get a backshins has services and APIs and actions
for saving restoring caches.
These are used in our critical for speeding up actions
that pull on the things from public indices.
These caches are key, and they can be restored
based on partial key matches.
So for example, if you have your us dependencies,
you want to restore it from multiple different sources,
if you've unfolded from multiple places.
And the way it was told is when you have partial matches,
this is a store is based on that partial match,
but also sort of a privilege system of anything
that's previously been landed in the upstream repo.
So if you can poison that upstream cache,
any downstream workflows that run
will run with your poison cache.
So here's the question, how does it get up?
No, which branch?
A cache frustration should come from.
Well, the answer is, the branch engine
is in a place of part of the cache key,
and it's computed on the client side.
How does get a backshins authenticate cache stores?
It was authenticated with an actions runtime token,
which is injected into the runner.
Set it from the GitHub token.
Combined, this is the perfect recipe for cache poisoning.
We have the credential we need to inject cache entries,
and we have a client side controlled cache prefix,
which we can tell the actions cache service
to cache with.
There's no authenticated domain separation
in other words between branches, which is hard to do,
because you would need then a way to do a delegated authority
between branches, meaning the branch
can collaborate with others' caches.
And all workflows in the repo
that run with the repos permissions
have that actions runtime token, which means
that even a very, very weak workflow,
when it has empty permissions, still has that cache token.
And it's all for success, which means
it's not run and evaluate runner-terry now,
which means you have six hours to affect your cache
poisoning attack.
So this was kind of my spoiled by the previous session,
but ultra-addicts is the perfect case study for this.
Really popular machine learning vision model,
68 million dollars from PIPI,
and they have many CSCD operations
that are intermediate by a body count.
It's not the vulnerability.
It's the exact same ones, it's our baby's step vulnerability.
They inject directly into the shell context
with the user control variable.
This is happening with the pull request target trigger,
which unlike a pull request,
runs with the upstream repos permissions.
So they've given this attacker upstream repo token permissions.
The attacker sends a pull request with that title,
which runs this payload, stills the cache token,
and goes on to smash the cache with a bunch of entries,
one of which eventually causes malicious PIPI upload.
So what's cool about this is that they didn't steal,
well, later on they steal, but at first,
they didn't even steal the PIPI upload token.
What they did was they poisoned the artifact
that was used to produce the upload token.
So this isn't visible from PIPI system.
So they do this, right?
Well, it's great is, and the risk is on fully on the eye
with the push event, meaning that it was pretty hard to notice
until someone actually knows that now we're on their system.
But, you know, what's for what it's response,
mission accomplished, they have deleted the,
they have, you know, triage and everything.
So I'll just several days later, it happens again.
And the reason for this is that, even though
they fixed the actual cache poisoning vulnerability
and the permissions, the insecure trigger they use,
they did not revoke their old credentials,
and so the attacker just stole the old credentials,
which they never revoked and did it again.
So, after you've been owned on,
get the actions, please revoke your credentials.
Okay, I don't have enough time for these takeaways.
Please look at the slides.
Okay, now I actually want to talk about talk,
which is hunting for bugs with this more.
So, then at the time, you can do this yourself if you want,
but here is this more, I'm gonna,
run it without all my knowledge,
because the Wi-Fi is kind of slow,
and I'm gonna run it against the two repos
that the actual attack was on.
And it's pretty fast, and there we go.
This is just every case where they,
the attacker could have injected,
the only need one of these, but, you know,
it's not so good.
Okay, technical details.
There's more as a typical audit tool architecture.
It has a preparation phase, an operation phase,
an aggregation phase, and then the output is,
like this rust, rusty, style, diagnostic.
Where I'll tell you what's spanned,
close to vulnerability.
Individuals, this is more audits,
our implementation of this audit trait,
has pretty standard stuff, and then there's various ways
you can define an audit.
It can be over a workflow, composite action,
or the individual steps are jobs,
so then I'll work flow, if you only need that level
of granularity.
There's actually a new one to this audit raw,
which can give the raw input to, as a strength,
if you need to do just like basic.
It's really, really, really basic rejects, matching.
Oh, that's big enough, good.
Okay, so that's an entire audit,
it can be really small.
This one's a secret inheritance audit,
all it does, it looks for places
where reusable work for use of secrets and herit,
which means that inheritance the entire secrets context
rather than being given a specific set of secrets,
meaning it violates the principle of use authority,
and then you can also do multiple locations
for finding.
So you can say, like, this step calls this action
with this set of subfields,
and that's why it produces such a nice sort
of spanning representation on the command line.
And yeah, if you want to contribute,
these audits are good examples of things
to start learning how to write this more audits.
Some technical challenges.
Like I said, it's all Yamohel under the hood,
so one of the questions is, how do we model,
it hubs complicated work flow and action contents,
to extremely large JSON schema that's public,
but it's very large and complicated,
and Cojun's support is limited and rust
for JSON schema.
Solution is we have this GitHub Actions models trait,
which has handwritten data models
for every single part of GitHub Actions,
which we then use generate those spans
for Yamohel spanning back to the source.
And then that follows the next question,
which is, how do we turn these symbolic Yamohel contents
like this step back into a span
that we run into the user?
An answer is, well, we couldn't with,
there's no real span preserving Yamohel
person to get for us,
and also sort of Yamohel's deprecated,
which still is, and we still have been on it,
but Yamohel path, which comprises that abstract path,
like, you know, job tests, steps, zero name,
back into a source level syntax span
for a pretty rendering.
Okay, take away,
stuff's complicated, has numerous security footguns,
and those uses normal devs,
and they should not be expecting to know these things.
You shouldn't have to know that GitHub has no
permission separation between branches
to use caches securely,
meaning that they're extra susceptible
to insecurity faults.
Moreover, offensive security research
and GitHub Actions is still pretty new as a field.
Really, the first visible public efforts
into this stuff started around 2021,
and new attacks and techniques are still being discovered.
For example, the cache poisoning attack
was just last summer.
And so, even though I've talked about some of this stuff,
it's not anywhere close to the end all,
or well-being the close to the end all of action security.
And, yes, of that.
It's also simultaneously kind of,
because it's just Yamohel,
and also very difficult to analyze,
because it's extremely dynamic in puts like contacts,
which can be loaded with pretty much anything,
including dynamic matrices that get loaded at runtime.
So those kinds of things are very difficult,
to analyze, and we try our best.
As part of that, you know, we detect many security pitfalls,
but not with perfect fidelity.
And this is, in part, because of design choices,
for example, favoring offline audit and we're possible,
but also because of that fundamental dynamic movement,
sort of models, normal computer of psychoanalysis.
You can't do certain things,
because the program is dynamic in your static.
Okay, so I spent running all that,
so I could have some time for questions.
Thank you all very much for listening.
If you want slides up here.
Thank you all very much for listening.
If you want slides up here.
All right.
Yes.
Thank you very much, though.
I have a quick question to down,
where to say, self-indulgence of a long line,
but I have one of the more of the five lines,
so I want to open to make this in my five lines.
Any force within this inside line,
where does one put an action that we're going to check?
Do real flow itself?
Yes.
So the question was, can I use this in my pipeline?
And the answer is yes.
So I showed the human output.
There's also a seraph output that it integrates
directly into GitHub.
And so you can see on GitHub a UI render of these findings.
OK, then, we'll see right in your pipeline.
Yes, exactly.
Yeah.
Yeah, on Guru uses it.
So you can use it to humber as a reference point
for using it and see it inside.
Yes.
Sort of a variation on the same question,
sorry about that.
Is it possible to read right above, right?
The same kind of article.
There's a code queue, which is GitHub own.
Yeah.
So the question was, can we rewrite the same kind of rules
using GitHub's code cool feature?
And answers they have.
GitHub two weeks ago introduced beta support
for GitHub actions all the time with code URL.
And I will say this, because I wrote this tool.
CodeQL is a much better fundamental system than what I wrote.
So if you want a system that is fundamentally built
or set a analysis, check it out.
The downside is that it's codeQL.
It's big.
It's complicated.
It's integrated.
So there's some trade-off there.
Yes.
If here, it could be called C9.
That's what the search phrase is.
If it is given by one of the problem,
the same word, it's not in any way.
Yeah.
The question was, is GitHub CI similar?
And answers, I have no idea.
Ha, ha, ha, ha, ha, ha.
Maybe.
I would love it if someone looked in sent me patches
to support GitHub CI.
Yes?
I mean, GitHub actions as well as the GitHub provided
actions are subject to regular updates.
How do you play on keeping up with this thing?
Yeah, so that's a question.
The question is, how do we keep up to date with the latest
changes to, for example, the official actions?
My long-term plan is to have a static representation
that we can upload via an HTTP, put it on HTTP
and server somewhere, how to static API to pull it down.
So if the default posture of an action changes,
clients that don't want to update this more itself,
can fetch that, and basically update the rules.
Currently, it's baked into the binary.
So currently, if actions changes to me fundamentally,
which they haven't really yet, I have to send a new release.
So it's probably going to get it.
Yeah.
Yes.
Please contribute.
All right.
Thank you very much.
Yeah.