Playbook

I decided it would be a good idea to start consolidating all the approaches and tactics I've been learning into a set of playbooks. Usually this is called a cheat sheet, but I want it to sound more fun - like an operations playbook! Anyhew, this is a pretty simple task to get going, I just need to add a section to the right-column of the site with a list of concise vectors and link them to a page containing a bag of tricks!

Distraction

While I was at it, in raw programmer mindset, I decide to automate the task of generating the navigation tree in the right-column as well. I thought it was awesomely nerdy to just display the output of tree there and make each row link to a blog post. It was hilariously simple to get going, I just dumped the output of tree into a <pre> block and wrapped each line with an <a> tag linking to each post. However, remembering to update that every time I add a new post is a mild inconvenience. Obviously I'll never actually gain much from automating it, but it's a fun task to take on while nursing a hang-over in bed. The blog posts for the site are stored in a top-level array, like this:

Blogs: []BlogPost{
    // Kioptricx Level 5
    BlogPost{
        Name:        "kioptrix_level_5",
        Title:       "Kioptrix Level 5 Writeup",
        Description: "Poorly configured apache, two vulnerable PHP applications, and an outdated kernel!",
        Image:       "/static/content/2020/july/kioptrix/5/2.webp",
        Path:        "2020/july/kioptrix_level_5.md",
        Date:        1595628205,
        Labels:      []string{"CTF"},
    },
    // Kioptricx Level 4
    BlogPost{
        Name:        "kioptrix_level_4",
        Title:       "Kioptrix Level 4 Writeup",
        Description: "More or less the same as the last, with an added sprinkle of shell escapeing",
        Image:       "/static/content/2020/july/kioptrix/4/2.webp",
        Path:        "2020/july/kioptrix_level_4.md",
        Date:        1595256298,
        Labels:      []string{"CTF"},
    }
}

The array is manually sorted from newest -> oldest, by nature of me just copy-pasting to the top of the declaration everytime I add a post. The server already automatically reads/renders the markdown and serves it at a dynamic path, so that's taken care of. The only piece that isn't automated is the generation of the navbar. The BlogPost struct is as follows:

// BlogPost - Declares data making up a markdown blog post
type BlogPost struct {
	// Path to the markdown file
	Path string
	// Name for matching against in URL and using as key in loaded content
	Name string
	// Post unix epoch
	Date int64
	// Labels
	Labels []string
	// Path to the image to use
	Image string
	// Rendered html string from converted markdown
	Content string
	// Title
	Title string
	// Description
	Description string
}

The intended structure of the navigation tree is like this:

|____2020
| |____July
| | |____kioptrix_level_5
| | |____kioptrix_level_4

So, on load of the site, this is what I came up with for generating it:

// LoadNavigationTree - Builds the navigation tree
func LoadNavigationTree(s *Server) {
	// Keep track of Year/Month nodes we've generated
	years := []string{}
	months := []string{}
	// Keep track of the current year/month we're placing blog posts under
	currentYear := ""
	currentMonth := ""
	// s.Blogs are sorted newest->oldest
	// Iterate over blogs
	for _, Blog := range s.Blogs {
		// Convert date epoch to time
		t := time.Unix(Blog.Date, 0)
		// Has the year changed this cycle?
		yearChanged := false
		// Resolve Year/Month strings from the Epoch
		year := fmt.Sprintf("%v", t.Year())
		month := fmt.Sprintf("%v", t.Month())
		// Is this a year node we haven't added yet?
		if _, found := Find(years, year); !found {
			// Is this not the first year node?
			if currentYear != "" {
				// Close previous month & year markup
				s.NavigationTree += "</div></div></br>"
			}
			// Update state
			currentYear = year
			yearChanged = true
			years = append(years, year)
			// Add markup for the start of the year node
			s.NavigationTree += fmt.Sprintf("<a data-toggle=\"collapse\" href=\"#collapse%[1]v\" role=\"button\" aria-expanded=\"false\" aria-controls=\"collapse%[1]v\"><pre class=\"navpre\">|____%[1]v</pre></a><div class=\"collapse divpre show\" id=\"collapse%[1]v\">", year)
		}
		// Is this a month node we haven't added yet?
		if _, found := Find(months, month); !found {
			// Did the last month node end this cycle without the year node also ending?
			if !yearChanged && currentMonth != "" {
				// Close previous month node
				s.NavigationTree += "</div></br>"
			}
			// If the year has changed this cycle, add a line-break before opening a month node
			if yearChanged {
				s.NavigationTree += "</br>"
			}
			// Update state
			currentMonth = month
			months = append(months, month)
			// Add starting markup for month node
			s.NavigationTree += fmt.Sprintf("<a data-toggle=\"collapse\" href=\"#collapse%[1]v\" role=\"button\" aria-expanded=\"false\" aria-controls=\"collapse%[1]v\"><pre class=\"navpre\">| |____%[1]v</pre></a><div class=\"collapse divpre\" id=\"collapse%[1]v\">", month)
		}
		// Add this blog nav
		s.NavigationTree += fmt.Sprintf("</br><a href=\"/blogs/%v\"><pre class=\"navpre\">| | |____%v</pre></a>", Blog.Name, Blog.Name)
	}
	// Close month & year
	s.NavigationTree += "</div></div></br>"
}

Pretty simple stuff! I like to try to focus on code readability in most cases, especially personal projects. There's certainly more efficient approaches I could have taken. This string is rendered to HTML and injected into the blog nav bar in each handler call. Sweet, there's some time wonderfully waisted!

Skeleton

Okay back to the task at hand now, I have to figure out how I want this to look from a high-level. I know I want it to fit into my existing 'template', so I'm going to stuff the navigation for this playbook into this markup:

<div class="row">
    <div class="col-md-4">
        <div class="card my-4">
            <h5 class="card-header">Playbook</h5>
            <div class="card-body">          
                </p>Place navigation content here</p>
            </div>
        </div>
    </div>
</div>

Which renders like this: 1 Now, I anticipate I'll have an ever-growing list of topics here as I wonder down the various paths that interest me, and I think just to start I'll likely have 6 or 7 topics I want to populate. I don't want this pane being massive, so how should I organize it. I generally suck at organizing website layouts, or with frontend tasks in general... I'd like it to pull inspiration from command-line tools if possible. Perhaps I could make a really simple "shell" in javascript? Like, have:

  • An input field styled to look like a shell
  • On pressing enter, send the command to a JS switch statement that populates the 'terminal'
  • ls would display available pages
  • ./<cmd> or <cmd> would navigate to the Playbook Cool, that seems simple and nerdy enough, I love it!

MVP

First step, come up with an input field that looks and feels like a terminal, should be easy enough. Just as a base-line, here's an un styled input element added to the card: 2 Now, we want users who have basic linux skills to get the idea/feeling that this is a shell, so let's drop some text before the input line: 3 Alright, I hate writing css so I'm not going to spend much more time here honestly... Here's what I've got so far:
Markup:

<div class="card my-4">
    <h5 class="card-header">Playbook</h5>
    <div class="card-body">
        <div style="font-size: small; background-color: #36454f; white-space: nowrap; overflow-x: auto; overflow-y: hidden;">          
            <p style="display: inline; color:red">root@salmonsec</p>
            <p style="display: inline; color: white">:</p>
            <p style="display: inline; color: blue">/root/playbooks</p>
            <p style="display: inline; color: white">$</p>
            <input type="text" class="playbookInput"></input>
        </div>
    </div>  
</div>

css:

   .playbookInput, .playbookInput:focus {
      background-color: #36454f; 
      color: white;
      border: none;
      outline: none;
   }

Honestly, I don't even want to muck about with how it looks more right now, moving onto functionality!

  • Add a div to display the result text
  • Add an ID to the input field
  • Add an event handler for when it gets submitted Alright, first I pulled some in-line css out into classes, added ids to elements I'll need to access in JS and added the extra div:
<div class="card my-4">
    <h5 class="card-header">Playbook</h5>
    <div class="card-body">
        <div class="playbookTerminalInputDiv">          
            <p style="display: inline; color:red">root@salmonsec</p>
            <p style="display: inline; color: white">:</p>
            <p style="display: inline; color: blue">/root/playbooks</p>
            <p style="display: inline; color: white">$</p>
            <input type="text" class="playbookInput" id="PlaybookInput"></input>
        </div>
        <div class="playbookTerminalOutputDiv" id="PlaybookOutput"></div>
    </div>  
</div>

Then I Wrote JS to handle it:

// Bind to submit event of input
$("#PlaybookInput").keypress(function(e){
    // Enter pressed?
    if(e.which == 10 || e.which == 13) {
        // Grab 'command'
        let cmd = $("#PlaybookInput").val();
        // Switch on command entered
        switch(cmd) {
            case "ls":
                $("#PlaybookOutput").html(`<p>linux_privilege_escalation sql_injection</p>`);
                break;
            default:
                $("#PlaybookOutput").html(`<p>bash: ${cmd}: command not found</p>`);
        }
    }
})

4 5

Features, features, FEATURES!

Alright here's a list of things I want to go implement:

  • Make the terminal have a max-height and overflow scroll
  • On submit, append to the output div to act more like a terminal
  • On pressing enter, clear the current command
  • On pressing enter, auto-scroll back to entry-line
  • Add tree
  • Add help
  • Add "MOTD" to guide user initially
  • Add navigating to the playbook pages on command
  • tab completion for playbooks Aaaanndd here's the final javascript!
// On page load, redirect jQuery to $ and execute the script
(function($) {
    // Available playbooks
    const playbooks = ["linux_privilege_escalation","sql_injection"]
    // Rebind tab key event on body so we can remap it's function here
    // as tab-complete for our 'terminal'
    $('body').keydown(function(e) {
        var code = e.keyCode || e.which;
        if (code == '9' && $("#PlaybookInput").is(":focus")) {
            // Capture 'command'
            let cmd = sanitize($("#PlaybookInput").val());
            // Prevent default tab action
            e.preventDefault();
             // Iterate over available commands
             let matched = [];
             for(let i = 0; i<playbooks.length; i++) {
                 if (playbooks[i].startsWith(cmd)) {
                     matched.push(playbooks[i]);
                 }
             }
             if ( matched > 1 ) {
                 WriteOutputLine(cmd,matched.toString().replace(',',' '));
             }else{
                 $("#PlaybookInput").val(matched[0]);
             }
             return false;
        }
        
     });
    // Bind to submit event of input
    $("#PlaybookInput").keypress(function(e){
        // Grab 'command'
        let cmd = sanitize($("#PlaybookInput").val());
        
        // Enter pressed?
        if(e.which == 10 || e.which == 13) {
            // Switch on command entered
            switch(cmd) {
                case "help":
                    WriteOutputLine(cmd, "Try running ls or tree, each 'file' is an 'executable' linking to a page.");
                    break;
                case "ls":
                    WriteOutputLine(cmd, `${playbooks.toString().replace(',',' ')}`);
                    break;
                case "tree":
                    WriteOutputLine(cmd, `.</br>├── ${playbooks.toString().replace(',','</br>├── ')}`
                    )
                    break;
                default:
                    if (playbooks.includes(cmd))  {
                        window.location.href = `/playbook/${cmd}`;
                        WriteOutputLine(cmd, `Starting playbook ${cmd}`);
                    }else{
                        WriteOutputLine(cmd, `bash: ${cmd}: command not found`);
                    }
            }
            // Clear
            $("#PlaybookInput").val("");
            // Scroll to bottom
            $(".playbookTerminal").scrollTop($(".playbookTerminal").height()*$(".playbookTerminal").height());
        }
    })
    // Capture markup to output on new line 
    let inputDivClone = $(".playbookTerminalInputDiv").clone(true);
    inputDivClone.find("input").remove();
    let newLineHtml = inputDivClone.html();
    // Write line to output
    function WriteOutputLine(cmd, output) {
        $("#PlaybookOutput").append(newLineHtml+cmd+"</br>");
        $("#PlaybookOutput").append(output+"</br>");
    }
    // Attempt to prevent XSS
    function sanitize(string) {
        const map = {
            '&': '&amp;',
            '<': '&lt;',
            '>': '&gt;',
            '"': '&quot;',
            "'": '&#x27;',
            "/": '&#x2F;',
        };
        const reg = /[&<>"'/]/ig;
        return string.replace(reg, (match)=>(map[match]));
      }
})(jQuery);

I added some simple go code to handle the new routes, load the new markdown files at a separate place then the blogs etc. and we're all done! 6