New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add post module multi/gather/jenkins #8627
Conversation
Hi there, @thesubtlety, thanks for the submission and welcome! If you have any trouble or questions, please drop a note in here! |
Thanks, documentation added and fixed a number of the rubocop errors. Regarding the "complexity too high" issues - parsing text is really ugly. I'd rather not do a bunch of refactoring, but let me know if it's an issue. Also a number of "useless assignment variable" issues which might be false positives given we're parsing text values that may not exist and I wanted to ensure I wasn't operating on nil values. Let me know if you need anything else! Edit: just noticed the bit about |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestions and questions
|
||
def report_creds(user, pass) | ||
return if user.empty? || pass.empty? | ||
credential_data = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe the indention is off, here. The conditional statement above this line is a single line conditional statement
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah you're right, fixed.
xml_doc.xpath("//com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl").each do |node| | ||
username = "" | ||
password = "" | ||
description = "" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there some benefit to assigning the empty string to these variables before you assign them values?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nope, dumb error on my part.
username = "" | ||
description = "" | ||
passphrase = "" | ||
private_key = "" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same question as before; why assign empty string to then assign over.
|
||
begin | ||
k = OpenSSL::PKey::RSA.new(private_key, passphrase) | ||
key = SSHKey.new(k, :passphrase => passphrase, :comment => cred_id) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do please use the new ruby hash syntax:
key = SSHKey.new(k, passphrase: passphrase, comment: cred_id)
case session.type | ||
when 'meterpreter' | ||
configs = "" | ||
c = session.fs.file.search(path,"config.xml", recurse = true, timeout = -1).concat(session.fs.file.search(path, "build.xml", recurse = true, timeout = -1)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
c = session.fs.file.search(path, "config.xml", recurse = true, timeout = -1).concat(session.fs.file.search(path, "build.xml", recurse = true, timeout = -1))
(Space after first parameter)
Also, this seems like a good candidate for splitting to multiple lines.
I'm no ruby ninja, so I am curious as to what is gained by assigning variable values while passing the parameter? Is it some kind of documentation strategy?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Split to multiple lines. Yeah, no reason aside from me not knowing the APIs and including the variable purpose. I can clean it up.
@thesubtlety For the most part, don't worry too much about complexity warnings in rubocop. The whitespace (indention, spaces, etc) is far more important. I left some line comments/questions. Please take a look. I'm by no means a Ruby expert, so if I am wrong, let me know. |
Sounds good, thanks. Appreciate the feedback and patience! If you see questionable things it's likely due to my own ignorance so feel free to call it out. I'll commit here shortly. |
Sorry for the delay; there was a small get-together in the desert. I'm running into some issues testing this, and more than likely, it is due to my docker ignorance. If you could help me out, I would really appreciate it. First attempt:Target: Ubuntu 16.04x64 running Jenkins 2.60.2 in docker Installation commands:
msfconsole:
Second AttemptSame Ubuntu, same Jenkins, but I uploaded the payload manually and launched it as the regular username msfconsole
Third AttemptSame Ubuntu machine, but manual install of Jenkins msfconsole:
Not sure what I'm doing wrong. I assume that when I'm running the payload manually with the docker install, there's some docker magic preventing me from accessing the installation? |
@bwatters-r7 in your first attempt the TARGETURI is wrong. In that docker image the path to jenkins is In addition, Jenkins created the credentials.xml file while adding the first credentials (not to be confused with jenkins user). To add a credential goto : Jenkins -> Credentials -> System -> Global credentials (unrestricted) and then "Add Credentials". The error message is misleading because the file does not exist. |
No worries, we probably passed each other in the halls. Regarding Attempt 1 - verify your TARGETURI is correct. I can reproduce the error you're seeing if it's incorrect. With the following setup, my TARGETURI is just
Regarding Attempt 2 - that is odd. Running as the jenkins user? You can log into the docker image and verify the permissions etc.
Regarding Attempt 3 - this is running correctly it seems. If the install path is found and the token is decrypted, things are working as expected. The credentials.xml file won't be found if there are no credentials saved though and same with job configurations. Go to Edit: ninja'd by mpizala! |
Heh.... well crap. I absolutely should have seen the targeturi; you even had it in the steps! Sorry about that. Otherwise, it was my Jenkins ignorance that had me in trouble. Thanks for the quick correction, @mpizala and @thesubtlety! Testing
There a re a couple more things I want to poke at, but this gets me back on track. Thanks again! |
testing with database
|
@thesubtlety, I pushed this jenkins gather module, but would prefer to address the jenkins exploit fix in another PR. If you put one up, I'll go ahead and test/land today. |
Release NotesThe ability to gather saved Jenkins credentials, user tokens, SSH keys, and secrets has been added for Linux and Windows. |
Thanks @bwatters-r7 ! See #8816 for that fix. |
This post module can be used to extract saved Jenkins credentials, user tokens, SSH keys, and secrets from Linux and Window. Interesting files will be stored in loot along with combined csv output. This module also decrypts both Jenkins version 1 and 2 encrypted strings.
How it works
I'm not really a developer and this is my first post module, so I'm sure there's lots of room for improvement in the code.
Verification
Tested and working with both meterpreter and (cmd) shell on Linux and Windows, although I've found cmd shell on Windows to be hit or miss.
Tested on
Fixing jenkins_script_console to support Jenkins 2
As an aside to working with this post module, I noticed a bug in the jenkins_script_console exploit module. Not sure how I missed it when I added the API_TOKEN patch recently but sometime after Jenkins 2 was released the JSESSIONID implementation changed so for jenkins_script_console to work you'll need to change
line 183
sessionid = 'JSESSIONID' << res.get_cookies.split('JSESSIONID')[1].split('; ')[0]
tosessionid = 'JSESSIONID' << res.get_cookies.split('JSESSIONID')[2].split('; ')[0]
As a work around you can just specify a USERNAME and API_TOKEN.
Not sure if it warrants a PR given the quick fix - the following was working in my tests.
Exploit
Set up Jenkins on Docker to obtain a shell
Run
docker run -p 8080:8080 -p 50000:50000 jenkins
Default setup, install "suggested plugins", create new user admin, add a user or credential (via Manage Jenkins)
Start
msfconsole
Example Output
Jenkins 2.67 on ubuntu