Skip to main content

CodeSOD: Solve a Captcha to Continue

9 hours 14 minutes ago

The first time Z hit the captcha on his company's site, he didn't think much of it. And to be honest, the second time he wasn't paying that much attention. So it wasn't until the third time that he realized that the captcha had showed him the same image every single time- a "5" with lines scribbled all over it.

That led Z to dig out the source and see how the captcha was implemneted.

<Center>Click a number below to proceed to the next page. <br>Some browsers do not like this feature and will try to get around it. If you are having trouble<br> seeing the image, empty your internet cache and temporary internet files may help. <br>Please ensure you have no refresher add-ons installed on your browser.<br /> <table border=1><Tr><td colspan='3' align='center'> <font style='font-size:36px;'><img width='150' title='5' alt='5' src='valimages/5.gif'> </font></td></tr> <Tr> <Td align=center><font size='6'><A href='valid.php?got=crimied&linknum=1'>1</a></font></td> <Td align=center><font size='6'><A href='valid.php?got=crimied&linknum=2'>2</a></font></td> <Td align=center><font size='6'><A href='valid.php?got=crimied&linknum=3'>3</a></font></td> </tr> <Tr> <Td align=center><font size='6'><A href='valid.php?got=crimied&linknum=4'>4</a></font></td> <Td align=center><font size='6'><A href='valid.php?got=crimied&linknum=5'>5</a></font></td> <Td align=center><font size='6'><A href='valid.php?got=crimied&linknum=6'>6</a></font></td> </tr> <tr> <Td align=center><font size='6'><A href='valid.php?got=crimied&linknum=7'>7</a></font></td> <Td align=center><font size='6'><A href='valid.php?got=crimied&linknum=8'>8</a></font></td> <Td align=center><font size='6'><A href='valid.php?got=crimied&linknum=9'>9</a></font></td> </tr></table> </Center>

Look, I know there's a joke about how hard it is to center things in CSS, but I think we've gone a little overboard with our attempt here.

Now, the PHP driving this page could have easily been implemented to randomly select an image from the valimages directory, and there was some commented out code to that effect. But it appears that whoever wrote it couldn't quite understand how to link the selected image to the behavior in valid.php, so they just opted to hard code in five as the correct answer.

The bonus, of course, is that the image for five is named 5.gif, which means if anyone really wanted to bypass the captcha, it'd be trivial to do so by scraping the code. I mean, not more trivial than just realizing "it's the same answer every time", but still, trivial.

Of course, out here in the real world, captchas have never been about keeping bots out of sites, and instead are just a way to trick the world into training AI. Pretty soon we'll roll out the Voight-Kampf test, but again, the secret purpose won't be to find the replicants, but instead gather data so that the next generation of replicants can pass the test.

[Advertisement] Keep all your packages and Docker containers in one place, scan for vulnerabilities, and control who can access different feeds. ProGet installs in minutes and has a powerful free version with a lot of great features that you can upgrade when ready.Learn more.
Remy Porter

Error'd: Once Is Never Enough

3 days 9 hours ago

"Getting ready to!" anticipated richard h. but then this happened. "All I want are the CLI options to mark the stupid TOS box so I can install this using our Chef automation. "What are the options" is too much to ask, apparently. But this is Microsoft. Are stupid errors like this really that unexpected?"

 

Followed immediately by richard's report: "Following up my error'd submission a few minutes ago, I clicked the "Accept TOS" box, and the "Something unexpected happened" box lit up, so I clicked the button to let the unexpected do what the something wanted to do. Now I have successfully Something unexpected happened. smh Microsoft. "

 

An anonymous griper snickered "It's a made up word, but I just wanted to check the spelling before writing it in a slack comment as a joke referencing the show("that's a nice tnetennba"), but the first thing I saw was the AI preview with the first sentence incorrectly claiming it's "basketball" spelled backwards(which it's clearly not, backwards it would be "abnnetent" which is also not a word). " I have to differ, though. Spelled backwards it would be llabteksab.

 

And a different anonymous griper (I assume they're different, but they're anonymous so who can really know?) needed some help doing a quite trivial computation. "On which planet?" we all wonder together.

 

Finally, a recurring theme from a recurring reader, B.J.H. keeps buying stuff. "This screen shot was captured the morning of 26 October. I'm not sure what bothers me more, that the package was picked up twice (once in the future), or that "Standard Transit" (when the package should be expected) is a day before the pick-up. Or maybe they just lie about the pickup to cover for not meeting the standard delivery date. "

 

[Advertisement] BuildMaster allows you to create a self-service release management platform that allows different teams to manage their applications. Explore how!
Lyle Seaman

The Ghost Cursor

4 days 9 hours ago

Everyone's got workplace woes. The clueless manager; the disruptive coworker; the cube walls that loom ever higher as the years pass, trapping whatever's left of your soul.

But sometimes, Satan really leaves his mark on a joint. I worked Tech Support there. You may remember The C-Level Ticket. I'm Anonymous. This is my story.

Night after night, my dreams are full of me trying and failing at absolutely everything. Catch a bus? I'm already running late and won't make it. Dial a phone number to get help? I can't recall the memorized sequence, and the keypad's busted anyway. Drive outta danger? The car won't start. Run from a threat? My legs are frozen.

Then I wake up in my bed in total darkness, scared out of my skull, and I can't move for real. Not one muscle works. Even if I could move, I'd stay still because I'm convinced the smallest twitch will give me away to the monster lurking nearby, looking to do me in.

The alarm nags me before the sun's even seen fit to show itself. What day is it? Tuesday? An invisible, overwhelming dread pins me in place under the covers. I can't do it. Not again.

The thing is, hunger, thirst, and cold are even more nagging than the alarm. Dead tired, I force myself up anyway to do the whole thing over.

The office joe that morning was so over-brewed as to be sour. I tossed down the last swig in my mug, checking my computer one more time to make sure no Tech Support fires were raging by instant message or email. Then I threw on my coat and hat and quit my cube, taking the stairs to ground level.

I pushed open a heavy fire-escape door and stepped out into the narrow alley between two massive office buildings. Brisk autumn air and the din of urban motor traffic rushed to greet me. The dull gray sky above threatened rain. Leaning against the far brick wall were Toby and Reynaldo, a couple of network admins, hugging themselves as they nursed smoldering cigarettes. They nodded hello.

I tipped my hat in greeting, slipping toward the usual spot, a patch of asphalt I'd all but worn grooves in by that point. I lit my own cigarette and took in a deep, warming draw.

"Make it last another year," Toby spoke in a mocking tone, tapping ash onto the pavement. "I swear, that jerk can squeeze a nickel until Jefferson poops!"

An ambulance siren blared through the alley for a minute. The rig was no doubt racing toward the hospital down the street.

Reynaldo smirked. "You think Morty finally did it?"

Toby smirked as well.

I raised an eyebrow. "Did what?"

"Morty always says he's gonna run out into traffic one of these days so they can take him to the hospital and he won't have to be here," Reynaldo explained.

I frowned at the morbid suggestion. "Hell of a way to catch a break."

"Well, it's not like we can ask for time off," Toby replied bitterly. "They always find some way to rope us back in."

I nodded in sympathy. "You have it worse than we do. But my sleep's still been jacked plenty of times by 3AM escalated nonsense that shoulda been handled by a different part of the globe."

Reynaldo's eyes lit up fiercely. "They have all the same access and training, but it never falls on them! Yeah, been there."

The door swung open again, admitting a young woman with the weight of the world on her shoulders. This was Megan, a junior developer and recent hire. I tipped my hat while helping myself to another drag.

She hastened my way, pulling a pack of cigarettes from her handbag. With shaking hands, she fumbled to select a single coffin nail. "I quit these things!" she lamented. After returning the pack to her bag, she rummaged through it fruitlessly. "Dammit, where are those matches?!" She glanced up at me with a pleading expression.

I pulled the lighter from my coat pocket. "You sure?"

She nodded like she hadn't been more sure about anything in her entire life.

I lit it for her. She took a lung-filling pull, then exhaled a huge cloud of smoke.

"Goin' that well, huh?" I asked.

Megan also hugged herself, her expression pained. "Every major player in the industry uses our platform, and I have no idea how it hasn't all come crashing down. There are thousands of bugs in the code base. Thousands! It breaks all the time. Most of the senior devs have no clue what they're doing. And now we're about to lose the only guy who understands the scheduling algorithm, the most important thing!"

"That's tough." I had no idea what else to say. Maybe it was enough that I listened.

Megan glanced up nervously at the brewing storm overhead. "I just know that algorithm's gonna get dumped in my lap."

"The curse of competence." I'd seen it plenty of times.

"Ain't that the truth!" She focused on me again with a look of apology. "How've you been?"

I shrugged. "Same old, same old." I figured a fresh war story might help. "Had to image and set up the tech for this new manager's onboarding. Her face is stuck in this permanent glare. Every time she opens her mouth, it's to bawl someone out."

"Ugh."

"The crazy thing is, the walls of her office are completely covered with crucifixes, and all these posters plastered with flowers and hearts and sap like Choose Kindness." I leaned in and lowered my voice. "You know what I think? I think she’s an ancient Roman whose spite has kept her alive for over two thousand years. Those crosses are a threat!"

That teased a small laugh out of Megan. For a moment, the amusement reached her eyes. Then it was gone, overwhelmed by worry. She took to pacing through the narrow alley.

Back at my cube, I found a new urgent ticket at the top of my case load. Patricia Dracora, a senior project manager, had put in a call claiming her computer had been hacked. Her mouse cursor was moving around and clicking things all on its own.

It was too early in the morning for a case like this. That old dread began sneaking up on me again. The name put me on edge as well. Over the years, our paths had never crossed, but her nickname throughout Tech Support, Dracula, betrayed what everyone else made of her.

"Make like a leaf and blow!"

The boss barked his stern command over my shoulder. I stood and turned from my computer to find him at my cubicle threshold with arms folded, blocking my egress.

I couldn't blow, so I shrugged. "Can't be as bad as The Crucifier."

"Dracula's worse than The Crucifier," the boss replied under his breath in a warning tone. "For your own good, don't keep her waiting!" He tossed a thumb over his shoulder for good measure.

When he finally backed out of the way, I made tracks outta there. A few of my peers made eye contact as I passed, looking wary on my behalf.

The ticket pegged Dracora's office in a subfloor I'd never set foot in before. Descending the stairs, I had too much time to think. Of course I didn't expect a real hacking attempt. Peripheral hardware on the fritz, some software glitch: there'd be a simple explanation. What fresh hell would I have to endure to reach that point? That was what my tired brain couldn't let go of. The stimulants hadn't kicked in yet. With the strength of a kitten, I was stepping into a lion's den. A lion who might make me wish for crucifixion by the time it was all over.

From the stairwell, I entered a dank, deserted corridor. Old florescent lighting fixtures hummed and flickered overhead. That, combined with the overwhelming stench of paint fumes, set the stage for a ripping headache. There were no numbers on the walls to lead me to the right place. They must've taken them down to paint and never replaced them. I inched down worn, stained carpeting, peeking into each open gap I found to either side of me. Nothing but darkness, dust, and cobwebs at first. Eventually, I spotted light blaring from one of the open doors ahead of me. I jogged the rest of the way, eager to see any living being by that point.

The room I'd stumbled onto was almost closet-sized. It contained a desk and chair, a laptop docking station, and a stack of cardboard boxes on the floor. Behind the desk was a woman of short stature, a large purse slung over one shoulder. Her arms were folded as she paced back and forth in the space behind her chair. When I appeared, she stopped and looked to me wide-eyed, maybe just as relieved as I was. "Are you Tech Support?"

"Yes, ma'am." I entered the room. "What's—?"

"I don't know how it happened!" Dracora returned to pacing, both hands making tight fists around the straps of the purse she was apparently too wired and distracted to set down. "They made me move here from the fourth floor. I just brought everything down and set up my computer, and now someone has control of the mouse. Look, look!" She stopped and pointed at the monitor.

I rounded the desk. By the time I got there, whatever she'd seen had vanished. Onscreen, the mouse cursor sat still against a backdrop of open browsers and folders. Nothing unusual.

"It was moving, I swear!" Anguished, Dracora pleaded with me to believe her.

It seemed like she wasn't hostile at all, just stressed out and scared. I could handle that. "I'm sure we can figure this out, ma'am. Lemme have a look here."

I sat down at the desk and tried the wireless mouse first. It didn't work at all to move the cursor.

"The hacker's locked us out!" Dracora returned to pacing behind me.

As I sat there, not touching a thing, the mouse cursor shuttled across the screen like it was possessed.

"There! You see?"

Suddenly, somehow, my brain smashed everything together. "Ma'am, I have an idea. Could you please stand still?"

Dracora stopped.

I swiveled around in the chair to face her. "Ma'am, you said you were moving in down here. What's in your purse right now?"

Her visible confusion deepened. "What?"

"The mouse cursor only moves around when you do," I explained.

Her eyes widened. She dug deeply into her purse. A moment later, she pulled out a second wireless mouse. Then she looked to me like she couldn't believe it. "That's it?!"

"That's it!" I replied.

"Oh, lord!" Dracora replaced the dud sitting on her mousepad with the mouse that was actually connected to her machine, wilting over the desk as she did so. "I don't know whether to laugh or cry."

I knew the feeling. But the moment of triumph, I gotta admit, felt pretty swell. "Anything else I can help with, ma'am?"

"No, no! I've wasted enough of your time. Thank you so much!"

I had even more questions on the way back upstairs. With this huge, spacious office building, who was forcing Dracora to be in that pit? How had she garnered such a threatening reputation? Why had my experience been so different from everyone else's? I didn't mention it to the boss or my peers. I broke it all down to Megan in the alley a few days later.

"She even put in a good word for me when she closed the ticket," I told her. "The boss says I'm on the fast track for another promotion." I took a drag from my cigarette, full of bemusement. "I'm already as senior as it gets. The only way up from here is management." I shook my head. "That ain't my thing. Look how well it's gone for Dracora."

Megan lowered her gaze, eyes narrowed. "You said it yourself: the only reward for good work is more work."

And then they buried you ... in a basement, or a box.

I remembered being at the start of my career, like Megan. I remembered feeling horrified by all the decades standing between me and the day when I wouldn't or couldn't ever work again. A couple decades in, some part of me that I'd repressed had resurfaced. What the hell is this? What have I been doing?

Stop caring, a different part replied. Just stop caring. Take things day by day, case by case.

I'd obeyed for so long. Where had it gotten me?

Under my breath, I risked airing my wildest wish for the future. "Someday, I wanna break outta this joint."

Megan blinked up at me. I had her attention. "How?"

"I dunno," I admitted. "I gotta figure it out ... before I go nuts."

[Advertisement] Utilize BuildMaster to release your software with confidence, at the pace your business demands. Download today!
Ellis Morning

CodeSOD: A Basic Mistake

5 days 9 hours ago

Way back in 1964, people were starting to recgonize that computers were going to have a large impact on the world. There was not, at the time, very much prepackaged software, which meant if you were going to use a computer to do work, you were likely going to have to write your own programs. The tools to do that weren't friendly to non-mathematicians.

Thus, in 1964, was BASIC created, a language derived from experiments with languages like DOPE (The Dartmouth Oversimplified Programming Experiment). The goal was to be something easy, something that anyone could use.

In 1977, the TRS-80, the Commodore PET, and the Apple II all launched, putting BASIC into the hands of end users. But it's important to note that BASIC had already been seeing wide use for a decade on "big iron" systems, or more hobbyist systems, like the Altair 8800.

Today's submitter, Coyne, was but a humble student in 1977, and despite studying at a decent university, brand spanking new computers were a bit out of reach. Coyne was working with professors to write code to support papers, and using some dialect of BASIC on some minicomputer.

One of Coyne's peers had written a pile of code, and one simple segment didn't work. As it was just a loop to print out a series of numbers, it seemed like it should work, and work quite easily. But the programmer writing it couldn't get it to work. They passed it around to other folks in the department, and those folks also couldn't get it to work. What could possibly be wrong with this code?

3010 O = 45 3020 FOR K = 1 TO O 3030 PRINT K 3040 NEXT K

Now, it's worth noting, this particular dialect of BASIC didn't support long variable names- you could use a single letter, or you could use a letter and a number, and that was it. So the short variable names are not explicitly a problem here- that's just the stone tools which were available to programmers at the time.

For days, people kept staring at this block, trying to figure out what was wrong. Finally, Coyne took a glance, and in a moment was able to spot the problem.

I've done something nasty here, because I posted the correct block first. What the programmer had actually written was this:

3010 O = 45 3020 FOR K = 1 TO 0 3030 PRINT K 3040 NEXT K

The difference is subtle, especially when you're staring at a blurry CRT late at night in the computer lab, with too little coffee and too much overhead lighting. I don't know what device they were using for display; most terminals made sure to make O look different from 0, but I couldn't be bold enough to say all of them did. And, in this era, you'd frequently review code on printed paper, so who knows how it was getting printed out?

But that, in the end, was the problem- the programmer accidentally typed a zero where they meant the letter "O". And that one typo was enough to send an entire computer science department spinning for days when no one could figure it out.

In any case, it's interesting to see how an "easy" to use language once restricted variable names to such deep inscrutability.

[Advertisement] BuildMaster allows you to create a self-service release management platform that allows different teams to manage their applications. Explore how!
Remy Porter

CodeSOD: A Truly Bad Comparison

6 days 9 hours ago

For C programmers of a certain age (antique), booleans represent a frustrating challenge. But with the addition of stdbool.h, we exited the world of needing to work hard to interact with boolean values. While some gotchas are still in there, your boolean code has the opportunity to be simple.

Mark's predecessor saw how simple it made things, and decided that wouldn't do. So that person went and wrote their own special way of comparing boolean values. It starts with an enum:

typedef enum exop_t { EXOP_NONE, EXOP_AND, EXOP_OR, EXOP_EQUAL, EXOP_NOTEQUAL, EXOP_LT, EXOP_GT, EXOP_LEQUAL, EXOP_GEQUAL, EXOP_ADD, EXOP_SUBTRACT, EXOP_MULTIPLY, EXOP_DIV, EXOP_MOD, EXOP_NEGATE, EXOP_UNION, EXOP_FILTER1, EXOP_FILTER2 };

Yes, they did write an enum to compare booleans. They also wrote not one, but two functions. Let's start with the almost sane one.

static bool compare_booleans (bool bool1, bool bool2, exop_t exop) { int32_t cmpresult; if ((bool1 && bool2) || (!bool1 && !bool2)) { cmpresult = 0; } else if (bool1) { cmpresult = 1; } else { cmpresult = -1; } return convert_compare_result(cmpresult, exop); }

This function takes two boolean values, and a comparison we wish to perform. Then, we test if they're equal, though the way we do that is by and-ing them together, then or-ing that with the and of their negations. If they're equal, cmpresult is set to zero. If they're not equal, and the first boolean is true, we set cmpresult to one, and finally to negative one.

Thus, we're just invented strcmp for booleans.

But then we call another function, which is super helpful, because it turns that integer into a more normal boolean value.

static boolean convert_compare_result (int32_t cmpresult, exop_t exop) { switch (exop) { case EXOP_EQUAL: return (cmpresult) ? FALSE : TRUE; case EXOP_NOTEQUAL: return (cmpresult) ? TRUE : FALSE; case EXOP_LT: return (cmpresult < 0) ? TRUE : FALSE; case EXOP_GT: return (cmpresult > 0) ? TRUE : FALSE; case EXOP_LEQUAL: return (cmpresult <= 0) ? TRUE : FALSE; case EXOP_GEQUAL: return (cmpresult >= 0) ? TRUE : FALSE; default: printf( "ERR_INTERNAL_VAL\n" ); return TRUE; } }

We switch based on the requested operation, and each case is its own little ternary. For equality comparisons, it requires a little bit of backwards logic- if cmpresult is non-zero (thus true), we need to return FALSE. Also note how our expression enum has many more options than convert_compare_result supports, making it very easy to call it wrong- and worse, it returns TRUE if you call it wrong.

At least they made booleans hard again. Who doesn't want to be confused about how to correctly check if two boolean values are the same?

It's worth noting that, for all this code, the rest of the code base never used anything but EXOP_EQUAL and EXOP_NOTEQUAL, because why would you do anything else on booleans? Every instance of compare_booleans could have been replaced with a much clearer == or != operator. Though what should really have been replaced was whoever wrote this code, preferably before they wrote it.

[Advertisement] Utilize BuildMaster to release your software with confidence, at the pace your business demands. Download today!
Remy Porter

A Government Data Center

1 week ago

Back in the antediluvian times, when I was in college, people still used floppy disks to work on their papers. This was a pretty untenable arrangement, because floppy disks lost data all the time, and few students had the wherewithal to make multiple copies. Half my time spent working helldesk was breaking out Norton Diskutils to try and rescue people's term papers. To avoid this, the IT department offered network shares where students could store documents. The network share was backed up, tracked versions, and could be accessed from any computer on campus, including the VAX system (in fact, it was stored on the VAX).

I bring this up because we have known for quite some time that companies and governments need to store documents in centrally accessible locations so that you're not reliant on end users correctly managing their files. And if you are a national government, you have to make a choice: either you contract out to a private sector company, or you do it yourself.

South Korea made the choice to do it themselves, with their G-Drive system (short for Government Drive, no relation to Google Drive), a government file store hosted primarily out of a datacenter in Daejeon. Unfortunately, "primarily" is a bit too apropos- last month, a fire in that datacenter destroyed data.

The Interior Ministry explained that while most systems at the Daejeon data center are backed up daily to separate equipment within the same center and to a physically remote backup facility, the G-Drive’s structure did not allow for external backups. This vulnerability ultimately left it unprotected.

Someone, somehow, designed a data storage system that was structurally incapable of doing backups? And then told 750,000 government employees that they should put all their files there?

Even outside of that backup failure, while other services had backups, they did not have a failover site, so when the datacenter went down, the government went down with it.

In total, it looks like about 858TB of data got torched. 647 distinct services were knocked out. At least 90 of them were reported to be unrecoverable (that last link is from a company selling Lithium Ion safety products, but is a good recap). A full recovery was, shortly after the accident, predicted to take a month, but as of October 22, only 60% of services had been restored.

Now, any kind of failure of this scale means heads must roll, and police investigations have gone down the path of illegal subcontracting. The claim is that the government hired a contractor broke the law by subcontracting the work, and that those subcontractors were unqualified for the work they were doing- that while they were qualified to install or remove a li-ion battery, they were not qualified to move one, which is what they were doing and resulted in the fire.

I know too little about Korean laws about government contracting and too little about li-ion battery management to weigh in on this. Certainly, high-storage batteries are basically bombs, and need to be handled with great care and protected well. Though, if one knows how to install and uninstall a battery, moving a battery seems covered in those steps.

But if I were doing a root cause analysis here, while that could be the root cause of the fire, it is not the root cause of the outage. If you build a giant datacenter but can't replicate services to another location, you haven't built a reliable cloud storage system- you've just built an expensive floppy disk that is one trip too close to a fridge magnet away from losing all of your work. In this case, the fridge magnet was made of fire, but the result is the same.

I'm not going to say this problem would have be easy to avoid; actually building resilient infrastructure that fails gracefully under extreme stress is hard. But while it's a hard problem, it's also a well-understood problem. There are best practices, and clearly not one of them was followed.

[Advertisement] Keep all your packages and Docker containers in one place, scan for vulnerabilities, and control who can access different feeds. ProGet installs in minutes and has a powerful free version with a lot of great features that you can upgrade when ready.Learn more.
Remy Porter

Error'd: It's Daniel Time Again!

1 week 3 days ago

It's been several years now that our reliable contributor Daniel D. has been sending us the same gripe time after time. We get it, really we do. It irks us too, but it is astounding just how many times he's been able to find this!
With no further ado, here is Daniel's pet peeve. See if you can figure out what it is.

640 kB 40 characters must be enough for anybody," right? Daniel bemoaned "US Banks, brokers, financial institutions. You would expect them to put heavy safeguards with stronger the password the better, right? But then you find out they require just 8 to 40 and not a bit more."

 

Gripe Numero Dos, Daniel wants to know "How much is more?" Grumbling on, "I tried to register an account with FT.com with a generated password of 62 character length of mixed alphanumerical and special symbols, as usual, when this happened... Based on my experience this issue usually pops up when the system does not allow longer passwords. So, decreasing to 52 didn't work, 42 did. Maybe the mice know why."

 

"50 characters should be enough for everybody", Daniel complained in March. "Bill Gates supposedly said ... (he didn't really). But New Relic is sure that 50 characters password are safe enough and you don't need 62 characters at all."

 

Still longer ago, Daniel identified another transgression with a limit of 32 this time. No idea who to blame and shame for this one:

 

Finally (or initially), Daniel uncovered foolish consistency among the publishing gnomes: "We all know password requirements are like a plague. Even security professionals do not recommend complex password creation rules. But The Atlantic begs to differ: "Why don't you add a special character to your securely generated 62 characters long password?" Yep, The Atlantic, that special symbol is going to make difference and my account won't get hacked." Alas, Daniel, I regret to inform you that some IT departments don't get a choice about what requirements to enforce, their security auditors or regulations make that decision for them.

 


I bet you guessed it. [Advertisement] Utilize BuildMaster to release your software with confidence, at the pace your business demands. Download today!
Lyle Seaman

CodeSOD: This Is Really Empty

1 week 4 days ago

Konrad was trying to understand how an input form worked, and found this validation function.

function IsReallyEmpty($subject) { $trimmed = trim(preg_replace("/&.*;/", "", $subject)); return strlen($trimmed) != 0; }

Now, I can understand why one might want to have a different definition of "empty" when it comes to strings. An all whitespace string, like " " may rightfully be considered an empty input for many applications.

So calling trim makes a lot of sense. It's the preg_replace that starts to worry me, because that regex is clearly trying to match an HTML entity, aka &nbsp;. But it matches all HTML entities, not just ones like &nbsp; which are whitespace characters, but ampersands and greater/less-than signs.

But there's another problem with the regex. The * operator is greedy. So &nbsp;Hello World&nbsp; would see the opening &, the closing ; and decide the entire string could be rejected.

But that's not the real WTF. The real WTF is the very last line. In a function called IsReallyEmpty, it returns true if the input string is not empty, thus stretching the definition of "really" to new levels.

"Is this string really empty?" "No, it is."

[Advertisement] BuildMaster allows you to create a self-service release management platform that allows different teams to manage their applications. Explore how!
Remy Porter

CodeSOD: Forward Not Found

1 week 5 days ago

Anthony found this solution to handling 404 errors which um… probably shouldn't have been found.

function show_404($page = '') { $uri = $_SERVER['REQUEST_URI']; error_log("Caught 404: $uri"); $redirect_url = ""; switch($uri){ case "/SOMEURL": $redirect_url="http://www.SOMEWEBSITE.com/SOMEURL"; break; case "/SOMEOTHERURL": $redirect_url="http://www.SOMEWEBSITE.com/SOMEOTHERURL"; break; case "/YETANOTHERURL": $redirect_url="http://www.SOMEWEBSITE.com/YETANOTHERURL"; break; // ... THERE ARE 300 of these ... case "/MOREURLS": $redirect_url="http://www.SOMEWEBSITE.com/MOREURLS"; break; case "/EVENMOREURLS": $redirect_url="http://www.SOMEWEBSITE.com/EVENMOREURLS"; break; } if ($redirect_url){ Header( "HTTP/1.1 301 Moved Permanently" ); Header( "Location: $redirect_url" ); } else { parent::show_404($page); } }

Upon a 404 error, this code checks a switch statement. If the path portion (the REQUEST_URI) is in our switch, we redirect to a similar path on a different domain (I am only assuming it's a different domain; if it's the same domain, this is an entirely different class of WTF).

On the other hand, if we don't have an entry in our switch, we just show a 404 error.

I can see how this happened: someone migrated part of their site to a new domain, but didn't want to fix all the links (and can't fix all of people's bookmarks), so they wanted to automatically redirect the appropriate URLs. But here's the thing: this isn't the way to do it. In this particular case, the developers were using Code Igniter, a PHP framework for web apps, which has a routing engine; it would have been trivial to simply direct all the forwarded routes to a controller that just handles the redirects.

Or one of the many, many other options for redirecting users browsers that don't involve transforming a 404 into a 301 with a gigantic switch statement.

[Advertisement] Utilize BuildMaster to release your software with confidence, at the pace your business demands. Download today!
Remy Porter

CodeSOD: A Percentage of Refactoring

1 week 6 days ago

Joseph was doing a refactoring effort, merging some duplicated functions into one, cleaning up unused Java code that really should have been deleted ages ago, and so on. But buried in that pile of code that needed cleaning up, Joseph found this little bit of code, to validate that an input was a percentage.

@Override public Integer validatePercent(final String perc, final int currentPerc){ char[] percProc= perc.toCharArray(); char[] newPerc = new char[perc.length()]; int percent=0; int y=0; if(percProc.length>4){ return -1; } for(int x=0;x<percProc.length;x++){ if(Character.isDigit(percProc[x])){ newPerc[y]=percProc[x]; y++; } } if(y==0){ return -1; } String strPerc=(new String(newPerc)); strPerc=strPerc.trim(); if(strPerc.length()!=0){ percent=Integer.parseInt(strPerc); if(percent<0){ return -1; }else if(percent>100){ return -1; }else if(Integer.parseInt(strPerc)==currentPerc){ return -1; }else{ return Integer.parseInt(strPerc); } }else{ return-1; } }

This validation function takes a string and an integer as an input, and immediately copies the string into an array, and makes a bonus array that's empty to start.

We reject strings longer than 4 characters. Then, we iterate over our input array and check each character; if that character is a digit, we copy it into the newPerc array, otherwise we… don't. If we copied at least one character this way, we continue- otherwise we reject the input.

Which right off the bat, this means that we accept 5, .05 and .0A5.

We take our newPerc array and turn it back into a string, trimming off any whitespace (which I'm fairly certain whitespace isn't a digit, last I checked, so there's nothing to trim).

If the string is greater than 0 characters, we parse it into an integer. If the result is less than zero, we reject it. Fun fact, isDigit also doesn't consider - a digit, so there's no chance we have a negative number here. If it's greater than 100 we reject it. If, when we parse it into an integer a second time, it's equal to the currentPerc input parameter, we also reject it. Otherwise, we return the result of parsing the string into an integer a third time.

So this isn't truly a validate function. It's a parse function. A strange one that doesn't work the way any sane person would want. And most annoying, at least in Java land, is that it handles errors by returning a -1, letting the caller check the return value and decide how to proceed, instead of throwing an exception.

Also, why reject the input if it equals the current value? I'd say that I'll puzzle over that, but the reality is that I won't. It's a stupid choice that I'd rather just not think more about.

[Advertisement] Keep the plebs out of prod. Restrict NuGet feed privileges with ProGet. Learn more.
Remy Porter

Representative Line: The Batch Managing Batch File

2 weeks ago

Carl was debugging a job management script. The first thing that caught his attention was that the script was called file.bat. They were running on Linux.

The second thing he noticed, was that the script was designed to manage up to 999 jobs, and needed to simply roll job count over once it exceeded 999- that is to say, job 1 comes after job 999.

Despite being called file.bat, it was in fact a Bash script, and thus did have access to the basic mathematical operations bash supports. So while this could have been done via some pretty basic arithmetic in Bash, doing entirely in Bash would have meant not using Awk. And if you know how to use Awk, why would you use anything but Awk?

njobno=`echo $jobno | awk '{if ($0<999) {print $0 + 1} else { print 1 }}'`

As Carl writes: "I don't mind the desire to limit job count by way of mod(1000) but what an implementation!"

[Advertisement] Keep the plebs out of prod. Restrict NuGet feed privileges with ProGet. Learn more.
Remy Porter

Error'd: Domino Theory

2 weeks 3 days ago

Cool cat Adam R. commented "I've been getting a bunch of messages from null in my WhatsApp hockey group."

 

Shockingly big-handed Orion S. exclaimed "When I shared this with the sender, she offered to send me an (inf) next!" Lucky Orion didn't actually receive an (expl).

 

Mike S. mused "I've heard of Paris, Texas, but NULL, Texas...that's a new one. (from Monster.com)" Texas is a big place, Mike. There's bound to be at least one of everything there.

 

Some time ago, a couple of readers let us know about a major restaurant that had flubbed their website. We didn't run it at that time but since we're doing nulls today, chew on this thought: if Error'd doesn't hold the powerful multinationals to account, what will stop all the rest of the dominos from falling in a terrible pizza chain reaction?
Hyphenated Lincoln K-C reported "No redaction needed... nully I'm not null." and Emily bemoaned "This pizza is making me feel empty inside..."

 

Finally on this Friday, an anonymous dig at the software we all love/hate to hate/love. "Just to be clear, I have absolutely no trust issues with the null gadget. However, I don't see the 'Approve Access' button anywhere."

 

[Advertisement] ProGet’s got you covered with security and access controls on your NuGet feeds. Learn more.
Lyle Seaman

Representative Line: A Date Next Month

2 weeks 4 days ago

We all know the perils of bad date handling, and Claus was handed an annoying down bug in some date handling.

The end users of Claus's application do a lot of work in January. It's the busiest time of the year for them. Late last year, one of the veteran users raised a warning: "Things stop working right in January, and it creates a lot of problems."

Unfortunately, that was hardly a bug report. "Things stop working?" What does that even mean. Claus's team made a note of it, but no one thought too much about it, until January. That's when a flood of tickets came in, all relating to setting a date.

Now, this particular date picker didn't default to having dates, but let the users pick values like "Start of last month", or "Same day last year", or "Same day next month". And the trick to the whole thing was that sometimes setting dates worked just fine, sometimes it blew up, and it seemed to vary based on when you were setting the dates and what dates you were setting.

And let's take a look at one example of how this was implemented, which highlights why January was such a problem:

startDate = startDateString == "Start of last month" ? new DateTime(today.Year, today.Month-1, 1) : startDate;

They construct a new date out of the current date, by subtracting one from the Month of the current date. Which would never work for January.

And this pattern was used for all of their date handling. So "Same Day Next Month" worked fine- unless the current date was near the end of January, since they just added one to the month. Of course, "Same day next year" blows up on February 29th. And "Same day next/last week" works by adding or subtracting 7 from the day, which does not play nicely with month boundaries.

The fix was obvious: rewrite all of the code to do proper date arithmetic. The nuisance to the whole thing was that there were just a lot of "default" date arrangements that the software supported, so it was a surprising amount of effort to fix. And, given the axiom that "every change breaks somebody's workflow", there were definitely a few users who objected to the fixes- they were using the broken behavior as part of their workflow (and perhaps an excuse for why certain scheduling arrangements were "forbidden").

As a bonus complaint, I will say, I don't love the use of string literals to represent the different kinds of operations. It's certainly not the WTF here, but I am going to complain about it anyway.

[Advertisement] ProGet’s got you covered with security and access controls on your NuGet feeds. Learn more.
Remy Porter

A Refreshing Change

2 weeks 5 days ago

Dear Third-Party API Support,

You're probably wondering how and why your authorization server has been getting hammered every single day for more than 4 years. It was me. It was us—the company I work for, I mean. Let me explain.

I’m an Anonymous developer at Initech. We have this one mission-critical system which was placed in production by the developer who created it, and then abandoned. Due to its instability, it received frequent patches, but no developer ever claimed ownership. No one ever took on the task of fixing its numerous underlying design flaws.

About 6 months ago, I was put in charge of this thing and told to fix it. There was no way I could do it on my own; I begged management for help and got 2 more developers on board. After we'd released our first major rewrite and fix, there were still a few lingering issues that seemed unrelated to our code. So I began investigating the cause.

This system has 10+ microservices which are connected like meatballs buried deep within a bowl of spaghetti that completely obscures what those meatballs are even doing. Untangling this code has been a chore in and of itself. Within the 3 microservices dedicated to automated tasks, I found a lot of random functionality ... and then I found this!

See, our system extracts data from your API. It takes the refresh token, requests a new access token, and saves it to our database. Our refresh token to this system is only valid for 24 hours; as soon as we get access, we download the data. Before we download the data, we ensure we have a valid access token by refreshing it.

One of our microservice's pointless jobs was to refresh the access token every 5, 15, and 30 minutes for 22 of the 24 hours we had access to it. It was on a job timer, so it just kept going. Every single consent for that day kept getting refreshed, over and over.

Your auditing tools must not have revealed us as the culprit, otherwise we should've heard about this much sooner. You've probably wasted countless hours of your lives sifting through log files with a legion of angry managers breathing down your necks. I’m writing to let you know we killed the thing. You won’t get spammed again on our watch. May this bring you some closure.

Sincerely,

A Developer Who Still Cares

[Advertisement] Keep the plebs out of prod. Restrict NuGet feed privileges with ProGet. Learn more.
Ellis Morning

CodeSOD: The Bob Procedure

2 weeks 6 days ago

Joe recently worked on a financial system for processing loans. Like many such applications, it started its life many, many years ago. It began as an Oracle Forms application in the 90s. By the late 2000s, Oracle was trying to push people away from forms into their newer tools, like Oracle ApEx (Application Express), but this had the result of pushing people out of Oracle's ecosystem and onto their own web stacks.

The application Joe was working on was exactly that. Now, no one was going to migrate off of an Oracle database, especially because 90% of their business logic was wired together out of PL/SQL packages. But they did start using Java for developing their UI, and then at some other point, started using Liquibase for helping them maintain and manage their schema.

The thing about a three decade old application is that it often collects a lot of kruft. For example, this procedure:

CREATE OR REPLACE PROCEDURE BOB(p_str IN VARCHAR2) AS BEGIN dbms_output.put_line(p_str); END;

Bob here is just a wrapper around a basic print statement. Presumably, the original developer- I'm gonna go out on a limb and guess that dev was also named Bob- wanted a convenient debugging function while tracking down an error, and threw this into the codebase.

As WTFs go, the function isn't that interesting. But you know what is interesting? Its legacy. This procedure is older than any source control history the company has, which means it's been in the codebase for at least twenty years, but probably much longer. Nothing invokes it. It's survived a migration into SVN then into Git, countless database upgrades, a few (unfortunate) disaster recovery events, and still sits there, waiting to help Bob debug a problem in the database.

Joe writes:

I still don't know who Bob is. Nobody knew who Bob was when I asked around. But Bob, if you're still out there, just know that you are now cemented into a part of the software that hundreds of companies used to manage loans.

[Advertisement] Plan Your .NET 9 Migration with Confidence
Your journey to .NET 9 is more than just one decision.Avoid migration migraines with the advice in this free guide. Download Free Guide Now!
Remy Porter

CodeSOD: The File Transfer

3 weeks ago

SQL Server Integration Services is Microsoft's ETL tool. It provides a drag-and-drop interface for describing data flows from sources to sinks, complete with transformations and all sorts of other operations, and is useful for migrating data between databases, linking legacy mainframes into modern databases, or doing what most people seem to need: migrating data into Excel spreadsheets.

It's essentially a full-fledged scripting environment, with a focus on data-oriented operations. The various nodes you can drag-and-drop in are database connections, queries, transformations, file system operations, calls to stored procedures, and so on. It even lets you run .NET code inside of SSIS.

Which is why Lisa was so surprised that her predecessor had a "call stored procedure" node called "move file". And more than that, she was surprised that the stored procedure looked like this:

if (@doDelete = 1) begin set @cmdText = 'mv -f ' + @pathName + @FileName + @FileExt + ' ' + @pathName + 'archive\' + @FileName + @FileExt + '.archive' end else begin set @cmdText = 'cp -f ' + @pathName + @FileName + @FileExt + ' ' + @pathName + 'archive\' + @FileName + @FileExt + '.archive' end insert into #cmdOutput exec @cmdResult = master.dbo.xp_cmdshell @cmdText

This stored procedure was called from SSIS, which again, I want to stress, has the functionality to do this without calling a stored procedure. But this approach offers us a few unique "advantages".

First, it requires xp_cmdshell be enabled. This particular stored procedure is disabled by default, since it allows a user inside of SQL Server to invoke shell commands. Microsoft disables this by default, because it's a gaping security hole. Any security scanning tool you may run against your server will call it out as a big red flag. You're one SQL Injection attack away from an old rm -rf /.

Which, speaking of rm, you'll note the command strings they build to execute. mv and cp. Now, SQL Server can run on Linux, but this instance wasn't. No, the person responsible for this stored procedure also installed GNU Core Utils on Windows, just so they could have mv and cp to invoke from this stored procedure. Even better, they didn't document this dependency, so the first time someone tried to migrate the database to new hardware, this functionality broke and no one knew why.

At least the migration gave them a chance to update their SSIS packages to use the "File Transfer Task" instead of this stored procedure. But don't worry, there were plenty of other stored procedures using xp_cmdshell.

[Advertisement] Keep all your packages and Docker containers in one place, scan for vulnerabilities, and control who can access different feeds. ProGet installs in minutes and has a powerful free version with a lot of great features that you can upgrade when ready.Learn more.
Remy Porter

Error'd: Yes We Have No Bananas

3 weeks 3 days ago

There is fire sale on "Test In Production" incidents this week. (Ok, truth is that some of them are a little crusty and stale so we just mark them way down and push them all out at a loss). To be completely fair, testing in production is vitally important. If you didn't do that, the only way you'd know if something is broken is when one of your paying customers finds out. I call that testing in production the expensive way. The only WTFy thing about these is that when you test in production, your customers shouldn't stumble across the messes.

"We don't often test, but when we do it's always in production" snarked Brad W. unfairly. "My phone gave its default alert noise mixed with... some sound that made it seem like the phone was damaged. This was the alert that appeared. "

 

Next up, a smoking hot deal fresh off the press from Keith R. who gloated "Looks like Firestone Auto Care is testing in Production, and it's not going well! That's a lot more than 15/25/90 characters!"

 

Followed by a slightly older option from Crunchyroll, supplied by Miquel B. who reasoned "It's nice to see Crunchyroll likes to test in production every once in a while. Last time I caught them doing so, the live date happened to be the same week as the current week, but this time, I needed to look back a week on the release schedule to spot it. If I didn't have a reason to look back a week, I would not have noticed it this time."

 

Cole T. tersely typed "SL TEST DEFAULT".

 

"I misspelled my search term and found this pricey test item on a live webstore," reported Mark T.

 

Chemist Pieter learned about banana esters in high school chem class, but didn't expect banana testers in his daily news.

 

Reaching back a bit, Paul D. noted of Engadget that "Either someone was testing in production or was submitting before finishing their article. The article was retrieved via my RSS reader and I saw it a few days later. At that moment, the original post did not exist anymore."

 

And going even farther way back, we got a couple of simultaneous submissions about a leaky production test by Amazon Prime video, one from someone styled Larry and again by a Bastian H.

 

Finally, honestly, this Error'd at Honest.com is probably well past its best-by date. But we're throwing it in to the bag at no charge. Seth N. thought "Honest company got a little too honest with this pop up modal after logging in" but we think honesty is just the best policy.

 

[Advertisement] ProGet’s got you covered with security and access controls on your NuGet feeds. Learn more.
Lyle Seaman

CodeSOD: A JSON Serializer

3 weeks 4 days ago

Carol sends us today's nasty bit of code. It does the thing you should never do: serializes by string munging.

public string ToJSON() { double unixTimestamp = ConvertToMillisecondsSinceEpoch(time); string JSONString = "{\"type\":\"" + type + "\",\"data\":{"; foreach (string key in dataDict.Keys) { string value = dataDict[key].ToString(); string valueJSONString; double valueNumber; bool valueBool; if (value.Length > 2 && value[0].Equals('(') && value[value.Length - 1].Equals(')')) //tuples { char[] charArray = value.ToCharArray(); charArray[0] = '['; charArray[charArray.Length - 1] = ']'; if (charArray[charArray.Length - 2].Equals(',')) charArray[charArray.Length - 2] = ' '; valueJSONString = new string(charArray); } else if ((value.Length > 1 && value[0].Equals('{') && value[value.Length - 1].Equals('}')) || (double.TryParse(value, out valueNumber))) //embedded json or numbers { valueJSONString = value; } else if (bool.TryParse(value, out valueBool)) //bools { valueJSONString = value.ToLower(); } else //everything else is a string { valueJSONString = "\"" + value + "\""; } JSONString = JSONString + "\"" + key + "\":" + valueJSONString + ","; } if (dataDict.Count > 0) JSONString = JSONString.Substring(0, JSONString.Length - 1); JSONString = JSONString + "},\"time\":" + unixTimestamp.ToString() + "}"; return JSONString; }

Now, it's worth noting, C# already has some pretty usable JSON serialization built-ins. None of this code needs to exist in the first place. It's parsing across a dictionary, but that dictionary is itself constructed by copying properties out of an object.

What's fun in this is because everything's shoved into the dictionary and then converted into strings (for the record, the dictionary stores objects, not strings) the only way they have for sniffing out the type of the input is to attempt to parse the input.

If it starts and ends with a (), we convert it into an array of characters. If it starts and ends with {} or parses as a double, we just shove it into the output. If it parses as a boolean, we convert it to lower case. Otherwise, we throw ""s around it and put that in the output. Notably, we don't do any further escaping on the string, which means strings containing " could do weird things in our output.

The final delight to this is the realization that their method of handling appending will include an extra comma at the end, which needs to be removed. Hence the if (dataDict.Count > 0) check at the end.

As always, if you find yourself writing code to generate code, stop. Don't do it. If you find you actually desperately need to start by building a syntax tree, and only then render it to text. If you find yourself needing to generate JSON specifically, please, I beg you, just get a library or use your language's built-ins.

[Advertisement] Plan Your .NET 9 Migration with Confidence
Your journey to .NET 9 is more than just one decision.Avoid migration migraines with the advice in this free guide. Download Free Guide Now!
Remy Porter

A Unique Mistake

3 weeks 5 days ago

Henrik spent too many hours, staring at the bug, trying to understand why the 3rd party service they were interacting with wasn't behaving the way he expected. Henrik would send updates, and then try and read back the results, and the changes didn't happen. Except sometimes they did. Reads would be inconsistent. It'd work fine for weeks, and then suddenly things would go off the rails, showing values that no one from Henrik's company had put in the database.

The vendor said, "This is a problem on your side, clearly." Henrik disagreed.

So Henrik went about talking over the problem with his fellow devs, working with the 3rd party support, and building test cases which could reproduce the results reliably. It took many weeks of effort, but by the end, he was confident he could prove it was the vendor's issue.

"Hey," Henrik said, "I think these tests pretty convincingly show that it's a problem on your side. Let me know if the tests highlight anything for you."

The bug ticket vanished into the ether for many weeks. Eventually, the product owner replied. Their team had diagnosed the problem, and the root cause was that sometimes the API would get confused about record ownership and identity. It was a tricky problem to crack, but the product owner's developers had come up with a novel solution to resolve it:

Actually we could add a really unique id instead which would never get repeated, even across customers, it would stay the same for the entity and never be reused

And thus, the vendor invented primary keys and unique identifiers. Unfortunately, the vendor was not E.F. Codd, the year was not 1970, primary keys had been invented already, and in fact were well understood and widely used. But not, apparently, by this vendor.

[Advertisement] Plan Your .NET 9 Migration with Confidence
Your journey to .NET 9 is more than just one decision.Avoid migration migraines with the advice in this free guide. Download Free Guide Now!
Remy Porter

Representative Line: Listing Off the Problems

3 weeks 6 days ago

Today, Mike sends us a Java Representative Line that is, well, very representative. The line itself isn't inherently a WTF, but it points to WTFs behind it. It's an omen of WTFs, a harbinger.

ArrayList[] data = new ArrayList[dataList.size()];

dataList is an array list. This line creates an array of arraylists, equal in length to dataList. Why? It's hard to say for certain, but the whiff I get off it is that this is an attempt to do "object orientation by array(list)s." Each entry in dataList is some sort of object. They're going to convert each object in dataList into an arraylist of values. This also either is old enough to predate Java generics (which is its own WTF), or explicitly wants a non-generic version so that it can do this munging of values into an array.

Mike provided no further explanation, so that's all speculation. But what is true is that when I see a line like that, my programmer sense starting tingling- something is wrong, even if I don't quite know what.

[Advertisement] Picking up NuGet is easy. Getting good at it takes time. Download our guide to learn the best practice of NuGet for the Enterprise.
Remy Porter
Checked
2 hours 44 minutes ago
Curious Perversions in Information Technology
Subscribe to The Daily WTF feed