Milan looked to be on course for a comfortable victory after Rafael Leao fired home 16 minutes in before Bologna were dealt a further blow as Adama Soumaoro was sent off for bringing Rade Krunic down when he was through on goal four minutes later.
Captain Davide Calabria hammered Milan into a 2-0 lead in the 35th minute to put them in control before Ibrahimovic, starting his first league game of the season, put through his own net to give the hosts hope four minutes after the break.
Musa Barrow's fine finish three minutes later stunned the visitors to pull the 10-men level only for Roberto Soriano's red card before the hour mark for a reckless tackle to leave the Bologna with an even more difficult task.
They dug in and looked set to earn a hard-earned point but goals from Ismael Bennacer six minutes from time and a fine 90th-minute finish from 40-year-old Ibrahimovic saw Milan home.
After Marko Arnautovic had almost headed the home side into an early lead, it was all Milan in the first half, with Ibrahimovic, back in the side after recovering from a knee injury, creating the chance for Leao to break the deadlock.
With Soumaoro given his marching orders, Calabria's quick reactions to the loose ball took Milan into the break with a comfortable two-goal lead and a numerical advantage, but the match turned on its head in seven second half minutes.
The goal that got Bologna back into the match was a fortunate one as a corner was inadvertently diverted off the head of Ibrahimovic into his own goal, before Soriano slotted a pass to Barrow for the striker to level.
Soriano then thought he had got away with a studs-up challenge on Fode Ballo-Toure but, after VAR intervention, the referee brandished the red card once more.
Milan were frustrated time and again before Bennacer broke home hearts with a fine volley into the bottom corner, while Ibrahimovic had the final say as Milan made it eight wins and a draw from their nine league games so far.